001/* 002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package gololang.ir; 012 013import java.util.*; 014 015import org.eclipse.golo.compiler.PackageAndClass; 016 017import static java.util.Collections.unmodifiableSet; 018import static gololang.Messages.message; 019 020/** 021 * "classical" augmentation. 022 * <p> 023 * Represents all the augmentations applied to a type, i.e. functions and named augmentations 024 * applied with the {@code with} construct. 025 * <p> 026 * This represents code such 027 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 028 * augment MyType { 029 * function foo = |this| -> ... 030 * } 031 * </code></pre> 032 * or 033 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 034 * augment MyType with MyAugmentation 035 * </code></pre> 036 */ 037public final class Augmentation extends GoloElement<Augmentation> implements FunctionContainer, ToplevelGoloElement { 038 private final PackageAndClass target; 039 private final Set<GoloFunction> functions = new LinkedHashSet<>(); 040 private final Set<String> names = new LinkedHashSet<>(); 041 042 private Augmentation(PackageAndClass target) { 043 super(); 044 this.target = target; 045 } 046 047 protected Augmentation self() { return this; } 048 049 /** 050 * Creates an augmentation on the target name. 051 * 052 * @param target the name of the target (compatible with {@link PackageAndClass#of(Object)}) 053 * @return a classical augmentation 054 * @see PackageAndClass#of(Object) 055 */ 056 public static Augmentation of(Object target) { 057 return new Augmentation(PackageAndClass.of(target)); 058 } 059 060 /** 061 * Returns the target type of this augmentation. 062 * 063 * <p>Note that since resolution is done at runtime, the target is only referenced by its name (here a 064 * {@link PackageAndClass}). 065 */ 066 public PackageAndClass getTarget() { 067 if (target.packageName().isEmpty()) { 068 GoloModule mod = enclosingModule(); 069 if (mod != null) { 070 return mod.getTypesPackage().createSubPackage(target.className()); 071 } 072 } 073 return target; 074 } 075 076 @Override 077 public PackageAndClass getPackageAndClass() { 078 return getTarget(); 079 } 080 081 /** 082 * {@inheritDoc} 083 */ 084 @Override 085 public List<GoloFunction> getFunctions() { 086 return new ArrayList<>(functions); 087 } 088 089 /** 090 * {@inheritDoc} 091 */ 092 @Override 093 public void addFunction(GoloFunction func) { 094 if (func.getArity() == 0) { 095 throw new IllegalArgumentException(message("augment_function_no_args", func.getName(), this.getPackageAndClass())); 096 } 097 functions.add(makeParentOf(func)); 098 } 099 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 104 public boolean hasFunctions() { 105 return !functions.isEmpty(); 106 } 107 108 /** 109 * Returns the names of the applied named augmentations. 110 */ 111 public Set<String> getNames() { 112 return unmodifiableSet(names); 113 } 114 115 /** 116 * Checks if named augmentations were applied. 117 */ 118 public boolean hasNames() { 119 return !names.isEmpty(); 120 } 121 122 /** 123 * Define the functions or named augmentations to add to the target. 124 * <p> 125 * The objects to add can be a function or a string representing the name of a named 126 * augmentation. 127 * 128 * <p>This is a builder method. 129 * 130 * @param objects functions or named augmentations to add 131 * @return this augmentation 132 */ 133 public Augmentation with(Object... objects) { 134 return with(java.util.Arrays.asList(objects)); 135 } 136 137 /** 138 * Define the functions or named augmentations to add to the target. 139 * <p> 140 * The objects to add can be a function or a string representing the name of a named 141 * augmentation. 142 * 143 * <p>This is a builder method. 144 * 145 * @param objects a collection of functions or named augmentations to add 146 * @return this augmentation 147 */ 148 public Augmentation with(Collection<?> objects) { 149 if (objects != null) { 150 for (Object o : objects) { 151 if (o instanceof String) { 152 names.add((String) o); 153 } else { 154 addElement(o); 155 } 156 } 157 } 158 return this; 159 } 160 161 /** 162 * Merge two augmentations applied to the same target. 163 * 164 * <p><strong>Warning:</strong>the functions contained in the other augmentation being <em>move</em> to this one and <em>not copied</em>, 165 * the other augmentation references functions but is no more their parent. Therefore, the other augmentation is not 166 * in a consistent state and should be dropped, since no more needed. 167 */ 168 public void merge(Augmentation other) { 169 if (!other.getTarget().equals(getTarget())) { 170 throw new IllegalArgumentException("Can't merge augmentations to different targets"); 171 } 172 if (other != this) { 173 this.names.addAll(other.getNames()); 174 addFunctions(other.getFunctions()); 175 } 176 } 177 178 @Override 179 public String toString() { 180 return String.format("Augmentation<target=%s, names=%s, functions=%s>", 181 getTarget(), 182 getNames(), 183 getFunctions()); 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) { 191 if (functions.contains(original)) { 192 functions.remove(original); 193 } else { 194 throw cantReplace(original, newElement); 195 } 196 addElement(newElement); 197 } 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override 203 public void accept(GoloIrVisitor visitor) { 204 visitor.visitAugmentation(this); 205 } 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override 211 public List<GoloElement<?>> children() { 212 LinkedList<GoloElement<?>> children = new LinkedList<>(functions); 213 return children; 214 } 215}