/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.geppetto.validation.runner; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.cloudsmith.geppetto.pp.PPPackage; import org.cloudsmith.geppetto.pp.dsl.adapters.PPImportedNamesAdapter.Location; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.xtext.naming.QualifiedName; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; /** * Interface for data describing exports per Module. TODO: BAD NAME - RENAME TO * AllModulesState * */ public class AllModuleReferences implements Serializable { /** * Describes an exported class. One Export describes the class itself and a * list of Exports describe the parameters. * */ public static class ClassDescription { private Export theClass; private Map<String, Export> theParameters; public ClassDescription(Export classExport, Map<String, Export> parameters) { theClass = classExport; theParameters = parameters; } /** * Obtains an Export for the class. To obtain the unqualified name use {@link Export#getLastNameSegment() }. * * @return An export describing the location of the class declaration */ public Export getExportedClass() { return theClass; } /** * Obtains the Parameters exported for the class. To obtain the * unqualified name of a parameter use the key of the map, or call {@link Export#getLastNameSegment() } * * @return */ public Map<String, Export> getExportedParameters() { return Collections.unmodifiableMap(theParameters); } } public interface Export extends Serializable { /** * The source text (if any) associated with a DefinitionArgument's value * expression. An implementation should trim the result from leading and * trailing whitespace. This is essentially the return of the sequence * of characters found in the file {@link #getFile()} starting at {@link #getStart()} and extending for {@link #getLength()} characters * trimmed of leading and trailing whitespace. * * @return the source text, or null if a DefinitionArgument has no * value. */ String getDefaultValueText(); /** * The EClass of the exported element (a PPPackage.Literals.xxx class, * or a PPTPPackage.Literals.xxx class). * * @return */ public EClass getEClass(); /** * The file from which the entry is exported. * * @return */ public File getFile(); /** * @return the last segment of the qualified name of this export */ public String getLastNameSegment(); /** * The length of the textual description (starting at {@link #getStart()}, or 0 if not available. * * @return */ public int getLength(); /** * The line on which the export is found, or -1 if not available. */ public int getLine(); /** * The qualified name in string form of the exported element. * * @return */ public String getName(); /** * @return the name as a string without the last name segment */ public String getNameWithoutLastSegment(); /** * The name of the parent the exported element "inherits from", or null * if this element does not inherit. The parent name is a qualified name * in string form. * * @return */ public String getParentName(); /** * The start offset of the definition in source (starting at the * beginning of the file), or -1 if not available. * * @return */ public int getStart(); } public interface ImportedName extends Serializable { /** * The importing file * * @return */ File getFile(); /** * The length of the source text of the imported * * @return */ int getLength(); /** * The line number of the first line where the reference occurs. * * @return */ int getLine(); /** * The qualified name of the imported * * @return */ String getName(); /** * The start offset of the imported * * @return */ int getStart(); } private static class ImportedNameImpl implements ImportedName { private static final long serialVersionUID = 1L; private File file; private String name; private int start; private int length; private int line; private ImportedNameImpl(File f, String name, int line, int start, int length) { this.file = f; this.name = name; this.start = start; this.length = length; this.line = line; } @Override public File getFile() { return file; } @Override public int getLength() { return length; } @Override public int getLine() { return line; } @Override public String getName() { return name; } @Override public int getStart() { return start; } } private static final long serialVersionUID = 1L; private Multimap<File, Export> exportMap; private Map<File, Multimap<File, Export>> importMap; private Map<File, Multimap<File, Export>> ambiguityMap; private Multimap<File, ImportedName> unresolvedImports; private File root; private Multimap<String, String> restricted; private final static Multimap<File, Export> EmptyExports = ArrayListMultimap.create(); private final static Multimap<File, String> EmptyUnresolved = ArrayListMultimap.create(); private final static Map<File, Multimap<File, Export>> EmptyImports = Collections.emptyMap(); private final static Multimap<String, String> EmptyRestricted = ArrayListMultimap.create(); /** * Creates an empty instance. */ public AllModuleReferences() { restricted = Multimaps.unmodifiableMultimap(EmptyRestricted); } private Map<String, Export> _collectParameters(Set<String> processed, Map<String, Export> result, String className, Map<String, Export> classes, Multimap<String, Export> parameters) { // if there was no parent if(className == null || className.length() < 1) return result; if(processed.contains(className)) return result; // circular processed.add(className); Export theClassExport = classes.get(className); if(theClassExport == null) return result; for(Export e : parameters.get(className)) { String pName = e.getLastNameSegment(); if(!result.containsKey(pName)) result.put(pName, e); } return _collectParameters(processed, result, theClassExport.getParentName(), classes, parameters); } /** * Add an ambiguity of e from exporting module to the importing module. * * @param importingModule * @param exportingModule * @param e */ public void addAmbiguity(File importingModule, File exportingModule, Export e) { if(ambiguityMap == null) ambiguityMap = Maps.newHashMap(); Multimap<File, Export> ambiguities = ambiguityMap.get(importingModule); if(ambiguities == null) { // only allow unique key-value combinations ambiguities = HashMultimap.create(); // imports = ArrayListMultimap.create(); ambiguityMap.put(importingModule, ambiguities); } if(e == null) { System.err.println("null Export found"); } ambiguities.put(exportingModule, e); } /** * Add an export of e from the given module. * * @param moduleDir * @param e */ public void addExport(File moduleDir, Export e) { if(exportMap == null) exportMap = ArrayListMultimap.create(); exportMap.put(moduleDir, e); } /** * Add an import of e from exporting module to the importing module. * * @param importingModule * @param exportingModule * @param e */ public void addImport(File importingModule, File exportingModule, Export e) { if(importMap == null) importMap = Maps.newHashMap(); Multimap<File, Export> imports = importMap.get(importingModule); if(imports == null) { // only allow unique key-value combinations imports = HashMultimap.create(); // imports = ArrayListMultimap.create(); importMap.put(importingModule, imports); } if(e == null) { System.err.println("null Export found"); } imports.put(exportingModule, e); } /** * @param importingModuleDir * @param uri * @param unresolved */ public void addUnresolved(File importingModuleDir, URI uri, Map<QualifiedName, List<Location>> unresolved, Function<QualifiedName, String> fQualifiedToString) { if(unresolved.isEmpty()) return; if(!uri.isFile()) return; final File file = new File(uri.toFileString()); if(unresolvedImports == null) { unresolvedImports = ArrayListMultimap.create(); } for(Entry<QualifiedName, List<Location>> e : unresolved.entrySet()) { final String name = fQualifiedToString.apply(e.getKey()); for(Location location : e.getValue()) unresolvedImports.put(importingModuleDir, // new ImportedNameImpl(file, name, location.getLength(), location.getOffset(), location.getLength())); } } private Map<String, Export> collectParameters(String className, Map<String, Export> classes, Multimap<String, Export> parameters) { Map<String, Export> result = Maps.newHashMap(); Set<String> processed = Sets.newHashSet(); return _collectParameters(processed, result, className, classes, parameters); } private String file2ContainerKey(File f) { String path = f.getPath(); if(!(path.equals("_pptp") || f.isAbsolute())) f = new File(root, path); return f.getPath(); } /** * Returns an exported class with the given name, or null if no such class * can be found. * * @param className * the fqn of the wanted class * @param visibleExports * - all exports to consider * @return */ public Export findExportedClass(String className, Iterable<Export> visibleExports) { if(className == null || className.length() < 1) return null; final EClass HostClassLiteral = PPPackage.Literals.HOST_CLASS_DEFINITION; for(Export e : visibleExports) { if(HostClassLiteral.isSuperTypeOf(e.getEClass()) && className.equals(e.getName())) return e; } return null; } /** * Returns an iterable iterating over all exported values from every module. * * @return */ public Iterable<Export> getAllExported() { return getExportMap().values(); } /** * Returns an unmodifiable Map of importing module to a {@link Multimap} of * ambiguously imported exports per module. To get what is ambiguously * imported from module B into module A perform result.get(A).get(B). * * @return */ public Map<File, Multimap<File, Export>> getAmbiguityMap() { return Collections.unmodifiableMap(ambiguityMap != null ? ambiguityMap : EmptyImports); // reuse "EmptyImports" } public List<ClassDescription> getClassDescriptions(Iterable<Export> visibleExports) { ArrayList<ClassDescription> result = Lists.newArrayList(); final EClass HostClassLiteral = PPPackage.Literals.HOST_CLASS_DEFINITION; final EClass DefinitionArgumentLiteral = PPPackage.Literals.DEFINITION_ARGUMENT; final Map<String, Export> classes = Maps.newHashMap(); final Multimap<String, Export> parameters = ArrayListMultimap.create(); for(Export e : visibleExports) { EClass eClass = e.getEClass(); if(HostClassLiteral.isSuperTypeOf(eClass)) { classes.put(e.getName(), e); } else if(DefinitionArgumentLiteral.isSuperTypeOf(eClass)) { parameters.put(e.getNameWithoutLastSegment(), e); } } for(String className : classes.keySet()) result.add(new ClassDescription(classes.get(className), // collectParameters(className, classes, parameters))); return result; } /** * Returns an Iterable for all Exports of a puppet class. The given iterable * exports should contain exports of wanted visibility e.g. {@link AllModuleReferences#getVisibleExports(File)} if * {@link AllModuleReferences#isVisibilityRestricted(File)} returns true and * only classes visible to content in the container represented by the given * File are wanted, or {@link AllModuleReferences#getAllExported() } if * visibility is not restricted (all non restricted have the same visibility * and it is always all exports). * * @param exports * @return */ public Iterable<Export> getClasses(Iterable<Export> exports) { return Iterables.filter(exports, new Predicate<Export>() { @Override public boolean apply(Export input) { return PPPackage.Literals.HOST_CLASS_DEFINITION.isSuperTypeOf(input.getEClass()); } }); } /** * Returns an unmodifiable {@link Multimap} containing exports per File, * where a given File is a reference to a module directory. * * @return */ private Multimap<File, Export> getExportMap() { return Multimaps.unmodifiableMultimap(exportMap != null ? exportMap : EmptyExports); } /** * Returns an unmodifiable Multimap mapping module directory to a Collection * of Export. The export collection represents what is exported from the * module (if the File is a module directory), the root, or the special file * "_pptp". * * @return An unmodifiable multimap from module to what is exported from * that module. */ public Multimap<File, Export> getExportsPerModule() { return getExportMap(); } /** * Returns an unmodifiable Map of importing module to a {@link Multimap} of * imported exports per module. To get what is imported from module B into * module A perform result.get(A).get(B). * * @return */ public Map<File, Multimap<File, Export>> getImportMap() { return Collections.unmodifiableMap(importMap != null ? importMap : EmptyImports); } public Iterable<String> getParameterNames(Export exportedClass, Iterable<Export> visibleExports) { return Iterables.transform(getParameters(exportedClass, visibleExports), new Function<Export, String>() { @Override public String apply(Export from) { return from.getLastNameSegment(); // return lastSegmentOfQualifiedName(from.getName()); } }); } public Iterable<Export> getParameters(Export exportedClass, Iterable<Export> visibleExports) { // figure out which parameters to include final List<Export> classes = Lists.newArrayList(); for(Export parent = exportedClass; parent != null; parent = findExportedClass( parent.getParentName(), visibleExports)) { if(classes.contains(parent)) break; // circular classes.add(parent); } final EClass DefinitionArgumentLiteral = PPPackage.Literals.DEFINITION_ARGUMENT; final Map<String, Export> parameters = Maps.newHashMap(); // final List<Export> parameters = Lists.newArrayList(); for(Export e : visibleExports) { if(!DefinitionArgumentLiteral.isSuperTypeOf(e.getEClass())) continue; for(Export c : classes) if(e.getName().startsWith(c.getName())) { String lastSegment = e.getLastNameSegment(); // String lastSegment = // lastSegmentOfQualifiedName(e.getName()); if(parameters.get(lastSegment) == null) parameters.put(e.getLastNameSegment(), e); // parameters.put(lastSegmentOfQualifiedName(e.getName()), // e); } } return parameters.values(); } /** * Returns an (unmodifiable) multimap describing the restricted visibility * of modules. If a module's directory is a key in the returned map, its * view is restricted to containers listed in the value for that key. * * @return */ public Multimap<String, String> getRestricted() { return restricted; } /** * Returns the root to use for relative lookups * * @return */ public File getRoot() { return root; } /** * Returns an unmodifiable {@link Multimap} of the full information Module * -> unresolved names/file/locations. * * @return */ public Multimap<File, ImportedName> getUnresolved() { return Multimaps.unmodifiableMultimap(unresolvedImports); } /** * Returns an unmodifiable {@link Multimap} of Module -> unresolved names. * * @return */ public Multimap<File, String> getUnresolvedMap() { if(unresolvedImports == null) return EmptyUnresolved; Multimap<File, String> result = ArrayListMultimap.create(); for(File moduleFile : unresolvedImports.keySet()) { result.putAll( moduleFile, Iterables.transform(unresolvedImports.get(moduleFile), new Function<ImportedName, String>() { @Override public String apply(ImportedName from) { return from.getName(); } })); } return result; } /** * Returns the exports visible to code in the given module. The * moduleDirectory may be relative in which case the root must have been * set. If module directory path is the special "_pptp" the content of the * target platform is obtained. If the moduleDirectory is the root path, all * non modular exports (from manifests and target contributions from ruby * code) not in any module. * * @param moduleDirectory * @return */ public Iterable<Export> getVisibleExports(File moduleDirectory) { // make absolute if relative and not _pptp String containerKey = file2ContainerKey(moduleDirectory); // restricted returns iterator over lookup of all visible handles if(restricted.containsKey(containerKey)) { Iterable<String> r = restricted.get(containerKey); return Iterables.concat(Iterables.transform(r, new Function<String, Iterable<Export>>() { @Override public Iterable<Export> apply(String from) { return getExportMap().get(new File(from)); } })); } return getExportMap().values(); } /** * Returns true if the given moduleDirectory has limited visibility into all * exports. This is useful to know as the set of moduleDirectories with full * visibility see the same set of exports. * * @param moduleDirectory * @return */ public boolean isVisibilityRestricted(File moduleDirectory) { return restricted.containsKey(file2ContainerKey(moduleDirectory)); } public void setRestricted(Multimap<String, String> restricted) { if(restricted == null) throw new IllegalArgumentException("null 'restricted'"); this.restricted = Multimaps.unmodifiableMultimap(restricted); } /** * Sets the root file to allow relative lookup later. */ public void setRoot(File root) { this.root = root; } }