com.puppetlabs.geppetto.validation.runner.AllModulesState.java Source code

Java tutorial

Introduction

Here is the source code for com.puppetlabs.geppetto.validation.runner.AllModulesState.java

Source

/**
 * Copyright (c) 2013 Puppet Labs, 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:
 *   Puppet Labs
 */
package com.puppetlabs.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.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;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.dsl.adapters.PPImportedNamesAdapter.Location;

/**
 * Interface for data describing exports per Module.
 */
public class AllModulesState 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 static class ImportedName implements Serializable {
        private static final long serialVersionUID = 1L;

        private final File file;

        private final String name;

        private final int start;

        private final int length;

        private final int line;

        private ImportedName(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;
        }

        public File getFile() {
            return file;
        }

        public int getLength() {
            return length;
        }

        public int getLine() {
            return line;
        }

        public String getName() {
            return name;
        }

        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 AllModulesState() {
        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 ImportedName(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 AllModulesState#getVisibleExports(File)} if
     * {@link AllModulesState#isVisibilityRestricted(File)} returns true and
     * only classes visible to content in the container represented by the given
     * File are wanted, or {@link AllModulesState#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;

    }

}