Package org.apache.felix.ipojo.manipulator

Source Code of org.apache.felix.ipojo.manipulator.Pojoization

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.ipojo.manipulator;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import org.apache.felix.ipojo.manipulation.InnerClassManipulator;
import org.apache.felix.ipojo.manipulation.Manipulator;
import org.apache.felix.ipojo.manipulation.MethodCreator;
import org.apache.felix.ipojo.manipulation.annotations.MetadataCollector;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.xml.parser.ParseException;
import org.apache.felix.ipojo.xml.parser.SchemaResolver;
import org.apache.felix.ipojo.xml.parser.XMLMetadataParser;
import org.objectweb.asm.ClassReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
* Pojoization allows creating an iPOJO bundle from a "normal" bundle.
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class Pojoization {

    /**
     * iPOJO Imported Package Version.
     */
    public static final String IPOJO_PACKAGE_VERSION = " 1.8.0";

    /**
     * List of component types.
     */
    private List m_components;

    /**
     * Metadata (in internal format).
     */
    private List/*Element*/ m_metadata = new ArrayList/*Element*/();

    /**
     * Errors which occur during the manipulation.
     */
    private List m_errors = new ArrayList();

    /**
     * Warnings which occur during the manipulation.
     */
    private List m_warnings = new ArrayList();

    /**
     * Class map (class name, byte[]).
     */
    private Map m_classes = new HashMap();

    /**
     * Referenced packages by the composite.
     */
    private List m_referredPackages;

    /**
     * Flag describing if we need or not compute annotations.
     * By default, compute the annotations.
     */
    private boolean m_ignoreAnnotations;

    /**
     * Flag describing if we need or not use local XSD files
     * (i.e. use the {@link SchemaResolver} or not).
     * If <code>true</code> the local XSD are not used.
     */
    private boolean m_ignoreLocalXSD;

    /**
     * Input jar file.
     */
    private JarFile m_inputJar;

    /**
     * The manipulated directory.
     */
    private File m_dir;

    /**
     * The manifest location.
     */
    private File m_manifest;

    /**
     * Manifest attribute filter, with default iPOJO filters
     */
    private List/*IManifestAttributeFilter*/ m_manifestAttributeFilters = new ArrayList/*IManifestAttributeFilter*/();

    /**
     * Add an error in the error list.
     * @param mes : error message.
     */
    protected void error(String mes) {
        System.err.println(mes);
        m_errors.add(mes);
    }

    /**
     * Add a warning in the warning list.
     * @param mes : warning message
     */
    public void warn(String mes) {
        m_warnings.add(mes);
    }

    public List getErrors() {
        return m_errors;
    }

    /**
     * Activates annotation processing.
     */
    public void disableAnnotationProcessing() {
        m_ignoreAnnotations = true;
    }

    /**
     * Activates the entity resolver loading
     * XSD files from the classloader.
     */
    public void setUseLocalXSD() {
        m_ignoreLocalXSD = false;
    }

    /**
     * Manipulates an input bundle.
     * This method creates an iPOJO bundle based on the given metadata file.
     * The original and final bundles must be different.
     * @param in the original bundle.
     * @param out the final bundle.
     * @param metadata the iPOJO metadata input stream.
     */
    public void pojoization(File in, File out, InputStream metadata) {
        parseXMLMetadata(metadata);
        if (m_metadata == null) { // An error occurs during the parsing.
            return;
        }
        // m_metadata can be either an empty array or an Element
        // array with component type description. It also can be null
        // if no metadata file is given.

        try {
            m_inputJar = new JarFile(in);
        } catch (IOException e) {
            error("The input file " + in.getAbsolutePath() + " is not a Jar file");
            return;
        }

        // Get the list of declared component
        computeDeclaredComponents();

        // Start the manipulation
        manipulateJarFile(out);

        // Check that all declared components are manipulated
        for (int i = 0; i < m_components.size(); i++) {
            ComponentInfo ci = (ComponentInfo) m_components.get(i);
            if (!ci.m_isManipulated) {
                error("The component " + ci.m_classname + " is declared but not in the bundle");
            }
        }
    }

    /**
     * Manipulates an input bundle.
     * This method creates an iPOJO bundle based on the given metadata file.
     * The original and final bundles must be different.
     * @param in the original bundle.
     * @param out the final bundle.
     * @param metadataFile the iPOJO metadata file (XML).
     */
    public void pojoization(File in, File out, File metadataFile) {
        // Get the metadata.xml location if not null
        if (metadataFile != null) {
            parseXMLMetadata(metadataFile);
        }

        try {
            m_inputJar = new JarFile(in);
        } catch (IOException e) {
            error("The input file " + in.getAbsolutePath() + " is not a Jar file");
            return;
        }

        // Get the list of declared component
        computeDeclaredComponents();

        // Start the manipulation
        manipulateJarFile(out);

        // Check that all declared components are manipulated
        for (int i = 0; i < m_components.size(); i++) {
            ComponentInfo ci = (ComponentInfo) m_components.get(i);
            if (!ci.m_isManipulated) {
                error("The component " + ci.m_classname + " is declared but not in the bundle");
            }
        }
    }

    /**
     * Manipulates an expanded bundles.
     * Classes are in the specified directory.
     * this method allows to update a customized manifest.
     * @param directory the directory containing classes
     * @param metadataFile the metadata file
     * @param manifestFile the manifest file. <code>null</code> to use directory/META-INF/MANIFEST.mf
     */
    public void directoryPojoization(File directory, File metadataFile, File manifestFile) {
      // Get the metadata.xml location if not null
        if (metadataFile != null) {
            parseXMLMetadata(metadataFile);
        }

        if (directory.exists() && directory.isDirectory()) {
            m_dir = directory;
        } else {
            error("The directory " + directory.getAbsolutePath() + " does not exist or is not a directory.");
        }


        if (manifestFile != null) {
            if (manifestFile.exists()) {
                m_manifest = manifestFile;
            } else {
                error("The manifest file " + manifestFile.getAbsolutePath() + " does not exist");
            }
        }
        // If the manifest is not specified, the m_dir/META-INF/MANIFEST.MF is used.

        // Get the list of declared component
        computeDeclaredComponents();

        // Start the manipulation
        manipulateDirectory();

        // Check that all declared components are manipulated
        for (int i = 0; i < m_components.size(); i++) {
            ComponentInfo ci = (ComponentInfo) m_components.get(i);
            if (!ci.m_isManipulated) {
                error("The component " + ci.m_classname + " is declared but not in the bundle");
            }
        }

    }

    /**
     * Parse the content of the class to detect annotated classes.
     * @param inC the class to inspect.
     */
    private void computeAnnotations(byte[] inC) {
        ClassReader cr = new ClassReader(inC);
        MetadataCollector collector = new MetadataCollector();
        cr.accept(collector, 0);

        if (collector.isIgnoredBecauseOfMissingComponent()) {
          // No @Component, just skip.
            //warn("Annotation processing ignored in " + collector.getClassName() + " - @Component missing");
        } else if (collector.isComponentType()) {
            boolean toskip = false;
            for (int i = 0; !toskip && i < m_metadata.size(); i++) {
                Element meta = (Elementm_metadata.get(i);
                if (! meta.getName().equals("instance") // Only if its a component type definition,
                                                        // so skip instance declaration
                        && meta.containsAttribute("name")
                        && meta.getAttribute("name").equalsIgnoreCase(collector.getComponentTypeDeclaration().getAttribute("name"))) {
                    toskip = true;
                    warn("The component type " + collector.getComponentTypeDeclaration().getAttribute("name") + " is overriden by the metadata file");
                }
            }
            if (!toskip) {
                // if no metadata or empty one, create a new array.
                Element elem = collector.getComponentTypeDeclaration();
                m_metadata.add(elem);

                String name = elem.getAttribute("classname");
                name = name.replace('.', '/');
                name += ".class";

                // Creates the ComponentInfo and store bytecode
                ComponentInfo info = new ComponentInfo(name, elem);
                info.m_bytecode = inC;
                m_components.add(info);

                // Instantiate ?
                if (collector.getInstanceDeclaration() != null) {
                    //warn("Declaring an empty instance of " + elem.getAttribute("classname"));
                    m_metadata.add(collector.getInstanceDeclaration());
                }
            }
        }
    }

    /**
     * Copies an input stream into an output stream but does not close the streams.
     * @param in the input stream
     * @param out the output stream
     * @throws IOException if the stream cannot be copied
     */
    private static void copyStreamWithoutClosing(InputStream in, OutputStream out) throws IOException {
        byte[] b = new byte[4096];
        for (int n; (n = in.read(b)) != -1;) {
            out.write(b, 0, n);
        }
    }

    /**
     * Gets the class name from the jar entry.
     * This method handles the WAR case.
     * @param entry the entry
     * @return the class name
     */
    private String getClassNameForEntry(JarEntry entry) {
      // If we're manipulating a war file remove the WEB-INF/classes prefix.
      if (entry.getName().startsWith("WEB-INF/classes/")) {
          return entry.getName().substring("WEB-INF/classes/".length());
        } else {
          return entry.getName();
        }
    }

    /**
     * Manipulate the input bundle.
     * @param out final bundle
     */
    private void manipulateJarFile(File out) {
        manipulateComponents(); // Manipulate classes
        m_referredPackages = getReferredPackages();
        Manifest mf = doManifest(); // Compute the manifest

        // Create a new Jar file
        FileOutputStream fos = null;
        JarOutputStream jos = null;
        try {
            fos = new FileOutputStream(out);
            jos = new JarOutputStream(fos, mf);
        } catch (FileNotFoundException e1) {
            error("Cannot manipulate the Jar file : the output file " + out.getAbsolutePath() + " is not found");
            return;
        } catch (IOException e) {
            error("Cannot manipulate the Jar file : cannot access to " + out.getAbsolutePath());
            return;
        }

        try {
            // Copy classes and resources
            Enumeration entries = m_inputJar.entries();
            while (entries.hasMoreElements()) {
                JarEntry curEntry = (JarEntry) entries.nextElement();

                // If the class was manipulated, write out the manipulated
                // version of the bytecode

                String classEntry = getClassNameForEntry(curEntry);
                if (m_classes.containsKey(classEntry)) {
                    JarEntry je = new JarEntry(curEntry.getName());
                    byte[] outClazz = (byte[]) m_classes.get(classEntry);
                    if (outClazz != null && outClazz.length != 0) {
                        jos.putNextEntry(je); // copy the entry header to jos
                        jos.write(outClazz);
                        jos.closeEntry();
                    } else { // The class is already manipulated
                        jos.putNextEntry(curEntry);
                        InputStream currIn = m_inputJar.getInputStream(curEntry);
                        copyStreamWithoutClosing(currIn, jos);

                        currIn.close();
                        jos.closeEntry();
                    }
                } else {
                    // Do not copy the manifest
                    if (!curEntry.getName().equals("META-INF/MANIFEST.MF")) {
                        // copy the entry header to jos
                        jos.putNextEntry(curEntry);
                        InputStream currIn = m_inputJar.getInputStream(curEntry);
                        copyStreamWithoutClosing(currIn, jos);
                        currIn.close();
                        jos.closeEntry();
                    }
                }
            }
        } catch (IOException e) {
            error("Cannot manipulate the Jar file : " + e.getMessage());
            return;
        }

        try {
            m_inputJar.close();
            jos.close();
            fos.close();
            jos = null;
            fos = null;
        } catch (IOException e) {
            error("Cannot close the new Jar file : " + e.getMessage());
            return;
        }
    }

    /**
     * Manipulate the input directory.
     */
    private void manipulateDirectory() {
        manipulateComponents(); // Manipulate classes
        m_referredPackages = getReferredPackages();
        Manifest mf = doManifest(); // Compute the manifest
        if (mf == null) {
            error("Cannot found input manifest");
            return;
        }

        // Write every manipulated file.
        Iterator it = m_classes.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String classname = (String) entry.getKey();
            byte[] clazz = (byte[]) entry.getValue();
            // The class name is already a path
            File classFile = new File(m_dir, classname);
            try {
                setBytecode(classFile, clazz);
            } catch (IOException e) {
                error("Cannot manipulate the file : the output file " +  classname + " is not found");
                return;
            }
        }

        // Write manifest
        try {
            writeManifest(mf);
        } catch (IOException e) {
            error("Cannot write the manifest file : " + e.getMessage());
        }

    }

    /**
     * Manipulate classes of the input Jar.
     */
    private void manipulateComponents() {

        // 1. Discover components described with annotations
        // Only do this if annotations are enabled
        if (!m_ignoreAnnotations) {
            Enumeration entries = getClassFiles();

            while (entries.hasMoreElements()) {
                String curName = (String) entries.nextElement();
                try {
                    // Need to load the bytecode for each .class entry
                    byte[] in = getBytecode(curName);

                    // This method adds the class to the component list
                    // if that bytecode is annotated with @Component.

                    // We check the array size to avoid manipulating empty files
                    // produced by incremental compilers (like Eclipse Compiler)
                    if(in != null && in.length > 0) {
                        computeAnnotations(in);
                    } else {
                        error("Cannot compute annotations from " + curName + " : Empty file");
                    }
                } catch (IOException e) {
                    error("Cannot read the class : " + curName);
                    return;
                }
            }
        }

        // 2. Iterates over the list of discovered components
        // Note that this list includes components from metadata.xml AND from annotations

        for (int i = 0; i < m_components.size(); i++) {
            ComponentInfo info = (ComponentInfo) m_components.get(i);

            // Get the bytecode if necessary
            if (info.m_bytecode == null) {
                try {
                    info.m_bytecode = getBytecode(info.m_classname);
                } catch (IOException e) {
                    error("Cannot extract bytecode for component '" + info.m_classname + "'");
                    return;
                }
            }
            // Manipulate the original bytecode and store the modified one
            byte[] outClazz = manipulateComponent(info.m_bytecode, info);
            m_classes.put(info.m_classname, outClazz);

            // Are there any inner classes to be manipulated ?
            if (!info.m_inners.isEmpty()) {
                for (int k = 0; k < info.m_inners.size(); k++) {
                    String innerCN = (String) info.m_inners.get(k) + ".class";
                    try {
                        // Get the bytecode and start manipulation
                        byte[] innerClassBytecode = getBytecode(innerCN);
                        manipulateInnerClass(innerClassBytecode, innerCN, info);
                    } catch (IOException e) {
                        error("Cannot manipulate inner class '" + innerCN + "'");
                        return;
                    }
                }
            }
        }
    }

    /**
     * Gets a byte array that contains the bytecode of the given classname.
     * This method can be overridden by sub-classes.
     * @param classname name of a class to be read
     * @return a byte array
     * @throws IOException if the classname cannot be read
     */
    protected byte[] getBytecode(final String classname) throws IOException {

        InputStream currIn = null;
        byte[] in = new byte[0];
        try {
            // Get the stream to read
            currIn = getInputStream(classname);
            int c;

            // Fill the byte array with IS content
            while ((c = currIn.read()) >= 0) {
                byte[] in2 = new byte[in.length + 1];
                System.arraycopy(in, 0, in2, 0, in.length);
                in2[in.length] = (byte) c;
                in = in2;
            }
        } finally {
            // Close the stream
            if (currIn != null) {
                try {
                    currIn.close();
                } catch (IOException e) {
                    // Ignored
                }
            }
        }

        return in;
    }

    /**
     * Gets an input stream on the given class.
     * This methods manages Jar files and directories.
     * If also looks into WEB-INF/classes to support WAR files.
     * This method may be overridden.
     * @param classname the class name
     * @return the input stream
     * @throws IOException if the file cannot be read
     */
    protected InputStream getInputStream(String classname) throws IOException {
        if (m_inputJar != null) {
            // Fix entry name if needed
            if (! classname.endsWith(".class")) {
                classname += ".class";
            }
            JarEntry je = m_inputJar.getJarEntry(classname);
            if (je == null) {
              // Try in WEB-INF/classes (WAR files)
              je = m_inputJar.getJarEntry("WEB-INF/classes/" + classname);
              if (je == null) {
                // If still null, throw an exception.
                throw new IOException("The class " + classname + " connot be found in the input Jar file");
              }
            }
            return m_inputJar.getInputStream(je);
        } else {
            // Directory
            File file = new File(m_dir, classname);
            return new FileInputStream(file);
        }
    }

    /**
     * Gets the list of class files.
     * The content of the returned enumeration contains file names.
     * It is possible to get input stream on those file by using
     * {@link Pojoization#getInputStream(String)} method.
     * @return the list of class files.
     */
    private Enumeration getClassFiles() {
        Vector files = new Vector();
        if (m_inputJar != null) {
            Enumeration enumeration = m_inputJar.entries();
            while (enumeration.hasMoreElements()) {
                JarEntry je = (JarEntry) enumeration.nextElement();
                if (je.getName().endsWith(".class")) {
                    files.add(je.getName());
                }
            }
        } else {
            searchClassFiles(m_dir, files);
        }
        return files.elements();
    }

    /**
     * Navigates across directories to find class files.
     * Sub-classes can override this method to customize the searched
     * files.
     * @param dir the directory to analyze
     * @param classes discovered classes
     */
    protected void searchClassFiles(File dir, List classes) {
        File[] files = dir.listFiles();
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                searchClassFiles(files[i], classes);
            } else if (files[i].getName().endsWith(".class")) {
                classes.add(computeRelativePath(files[i].getAbsolutePath()));
            }
        }
    }

//    /**
//     * Manipulates an inner class.
//     * @param inputJar input jar
//     * @param je inner class jar entry
//     * @param innerClassName inner class name
//     * @param ci component info of the component owning the inner class
//     * @throws IOException the inner class cannot be read
//     */
//    private void manipulateInnerClass(JarFile inputJar, JarEntry je, String innerClassName, ComponentInfo ci) throws IOException {
//        InputStream currIn = inputJar.getInputStream(je);
//        byte[] in = new byte[0];
//        int c;
//        while ((c = currIn.read()) >= 0) {
//            byte[] in2 = new byte[in.length + 1];
//            System.arraycopy(in, 0, in2, 0, in.length);
//            in2[in.length] = (byte) c;
//            in = in2;
//        }
//        // Remove '.class' from class name.
//        InnerClassManipulator man = new InnerClassManipulator(ci.m_classname.substring(0, ci.m_classname.length() - 6), ci.m_fields);
//        byte[] out = man.manipulate(in);
//
//        m_classes.put(je.getName(), out);
//
//    }

    /**
     * Computes a relative path for the given absolute path.
     * This methods computes the relative path according to the directory
     * containing classes for the given class path.
     * @param absolutePath the absolute path of the class
     * @return the relative path of the class based on the directory containing
     * classes.
     */
    private String computeRelativePath(String absolutePath) {
        String root = m_dir.getAbsolutePath();
        String path = absolutePath.substring(root.length() + 1);
        return path.replace('\\', '/'); // To support Windows systems, the \ are replaced by /
        //return path.replace('/', File.separatorChar);
    }

    /**
     * Manipulates an inner class.
     * @param in input bytecode of the inner file to manipulate
     * @param cn the inner class name (ends with .class)
     * @param ci component info of the component owning the inner class
     * @throws IOException the inner class cannot be read
     */
    private void manipulateInnerClass(byte[] in, String cn, ComponentInfo ci) throws IOException {
        // Remove '.class' from class name.
        String name = ci.m_classname.substring(0, ci.m_classname.length() - 6);
        InnerClassManipulator man = new InnerClassManipulator(name, ci.m_fields);
        byte[] out = man.manipulate(in);

        m_classes.put(cn, out);

    }

    /**
     * Gets the manifest.
     * This method handles Jar and directories.
     * For Jar file, the input jar manifest is returned.
     * For directories, if specified the specifies manifest is returned.
     * Otherwise, try directory/META-INF/MANIFEST.MF
     * @return the Manifest.
     * @throws IOException if the manifest cannot be found
     */
    private Manifest getManifest() throws IOException {
        if (m_inputJar != null) {
            return m_inputJar.getManifest();
        } else {
            return new Manifest(getManifestInputStream());
        }
    }

    /**
     * Create the manifest.
     * Set the bundle imports and iPOJO-components clauses
     * @return the generated manifest.
     */
    private Manifest doManifest() {
        Manifest mf = null;
        try {
            mf = getManifest();
        } catch (IOException e) {
            // Could not happen, the input bundle is a bundle so must have a manifest.
            error("Cannot get the manifest : " + e.getMessage());
            return null;
        }
        Attributes att = mf.getMainAttributes();
        setImports(att); // Set the imports (add ipojo and handler namespaces
        setPOJOMetadata(att); // Add iPOJO-Component
        setCreatedBy(att); // Add iPOJO to the creators
        return mf;
    }

    /**
     * Manipulate a component class.
     * @param in : the byte array of the class to manipulate
     * @param ci : attached component info (containing metadata and manipulation metadata)
     * @return the generated class (byte array)
     */
    private byte[] manipulateComponent(byte[] in, ComponentInfo ci) {
        Manipulator man = new Manipulator();
        try {
            byte[] out = man.manipulate(in); // iPOJO manipulation
            ci.detectMissingFields(man.getFields()); // Detect missing field
            // Insert information to metadata
            ci.m_componentMetadata.addElement(man.getManipulationMetadata());
            ci.m_isManipulated = true;
            ci.m_inners = man.getInnerClasses();
            ci.m_fields = man.getFields().keySet();
            return out;
        } catch (IOException e) {
            error("Cannot manipulate the class " + ci.m_classname + " : " + e.getMessage());
            return null;
        }
    }

    /**
     * Return the list of "concrete" component.
     */
    private void computeDeclaredComponents() {
        List componentClazzes = new ArrayList();
        for (int i = 0; i < m_metadata.size(); i++) {
            Element meta = (Element) m_metadata.get(i);
            String name = meta.getAttribute("classname");
            if (name != null) { // Only handler and component have a classname attribute
                name = name.replace('.', '/');
                name += ".class";
                componentClazzes.add(new ComponentInfo(name, meta));
            }
        }
        m_components = componentClazzes;
    }

    /**
     * Component Info.
     * Represent a component type to be manipulated or already manipulated.
     * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
     */
    private class ComponentInfo {
        /**
         * Component Type metadata.
         */
        Element m_componentMetadata;

        /**
         * Component Type implementation class.
         */
        String m_classname;

        /**
         * Is the class already manipulated.
         */
        boolean m_isManipulated;

        /**
         * List of inner classes of the implementation class.
         */
        List m_inners;

        /**
         * Set of fields of the implementation class.
         */
        Set m_fields;

        /**
         * Initial (unmodified) bytecode of the component's class.
         * May be null !!
         */
        byte[] m_bytecode;

        /**
         * Constructor.
         * @param cn : class name
         * @param met : component type metadata
         */
        ComponentInfo(String cn, Element met) {
            this.m_classname = cn;
            this.m_componentMetadata = met;
            m_isManipulated = false;
        }

        /**
         * Detects missing fields.
         * If a referenced field does not exist in the class
         * the method throws an error breaking the build process.
         * @param fields : field found in the manipulated class
         */
        void detectMissingFields(Map fields) {
            // First, compute the list of referred fields
            List list = new ArrayList();
            computeReferredFields(list, m_componentMetadata);
            // Then, try to find each referred field in the given field map
            for (int i = 0; i < list.size(); i++) {
                if (!fields.containsKey(list.get(i))) {
                    error("The field " + list.get(i) + " is referenced in the "
                            + "metadata but does not exist in the " + m_classname + " class");
                }
            }
        }

        /**
         * Looks for 'field' attribute in the given metadata.
         * @param list : discovered field (accumulator)
         * @param metadata : metadata to inspect
         */
        private void computeReferredFields(List list, Element metadata) {
            String field = metadata.getAttribute("field");
            if (field != null && ! list.contains(field)) {
                list.add(field);
            }
            for (int i = 0; i < metadata.getElements().length; i++) {
                computeReferredFields(list, metadata.getElements()[i]);
            }
        }

    }

    /**
     * Set the create-by in the manifest.
     * @param att : manifest attribute.
     */
    private void setCreatedBy(Attributes att) {
        String prev = att.getValue("Created-By");
        if (prev == null) {
            att.putValue("Created-By", "iPOJO " + IPOJO_PACKAGE_VERSION);
        } else {
            if (prev.indexOf("iPOJO") == -1) { // Avoid appending iPOJO several times
                att.putValue("Created-By", prev + " & iPOJO " + IPOJO_PACKAGE_VERSION);
            }
        }
    }

    /**
     * Add imports to the given manifest attribute list. This method add ipojo imports and handler imports (if needed).
     * @param att : the manifest attribute list to modify.
     */
    private void setImports(Attributes att) {
        Map imports = parseHeader(att.getValue("Import-Package"));
        Map ver = new TreeMap();
        ver.put("version", IPOJO_PACKAGE_VERSION);
        if (!imports.containsKey("org.apache.felix.ipojo")) {
            imports.put("org.apache.felix.ipojo", ver);
        }
        if (!imports.containsKey("org.apache.felix.ipojo.architecture")) {
            imports.put("org.apache.felix.ipojo.architecture", ver);
        }
        if (!imports.containsKey("org.osgi.service.cm")) {
            Map verCM = new TreeMap();
            verCM.put("version", "1.2");
            imports.put("org.osgi.service.cm", verCM);
        }
        if (!imports.containsKey("org.osgi.service.log")) {
            Map verCM = new TreeMap();
            verCM.put("version", "1.3");
            imports.put("org.osgi.service.log", verCM);
        }

        // Add referred imports from the metadata
        for (int i = 0; i < m_referredPackages.size(); i++) {
            String pack = (String) m_referredPackages.get(i);
            imports.put(pack, new TreeMap());
        }

        // Write imports
        att.putValue("Import-Package", printClauses(imports, "resolution:"));
    }

    /**
     * Add iPOJO-Components to the given manifest attribute list. This method add the iPOJO-Components header and its value (according to the metadata) to the manifest.
     * @param att : the manifest attribute list to modify.
     */
    private void setPOJOMetadata(Attributes att) {
        StringBuffer meta = new StringBuffer();
        for (int i = 0; i < m_metadata.size(); i++) {
            Element metadata = (Element) m_metadata.get(i);
            meta.append(buildManifestMetadata(metadata, new StringBuffer()));
        }
        if (meta.length() != 0) {
            att.putValue("iPOJO-Components", meta.toString());
        }
    }

    /**
     * Standard OSGi header parser. This parser can handle the format clauses ::= clause ( ',' clause ) + clause ::= name ( ';' name ) (';' key '=' value )
     * This is mapped to a Map { name => Map { attr|directive => value } }
     *
     * @param value : String to parse.
     * @return parsed map.
     */
    public Map parseHeader(String value) {
        if (value == null || value.trim().length() == 0) {
            return new HashMap();
        }

        Map result = new HashMap();
        QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
        char del;
        do {
            boolean hadAttribute = false;
            Map clause = new HashMap();
            List aliases = new ArrayList();
            aliases.add(qt.nextToken());
            del = qt.getSeparator();
            while (del == ';') {
                String adname = qt.nextToken();
                if ((del = qt.getSeparator()) != '=') {
                    if (hadAttribute) {
                        throw new IllegalArgumentException("Header contains name field after attribute or directive: " + adname + " from " + value);
                    }
                    aliases.add(adname);
                } else {
                    String advalue = qt.nextToken();
                    clause.put(adname, advalue);
                    del = qt.getSeparator();
                    hadAttribute = true;
                }
            }
            for (Iterator i = aliases.iterator(); i.hasNext();) {
                result.put(i.next(), clause);
            }
        } while (del == ',');
        return result;
    }

    /**
     * Print a standard Map based OSGi header.
     *
     * @param exports : map { name => Map { attribute|directive => value } }
     * @param allowedDirectives : list of allowed directives.
     * @return the clauses
     */
    public String printClauses(Map exports, String allowedDirectives) {
        StringBuffer sb = new StringBuffer();
        String del = "";

        for (Iterator i = exports.entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry) i.next();
            String name = (String) entry.getKey();
            Map map = (Map) entry.getValue();
            sb.append(del);
            sb.append(name);

            for (Iterator j = map.entrySet().iterator(); j.hasNext();) {
                Map.Entry entry2 = (Map.Entry) j.next();
                String key = (String) entry2.getKey();

                // Skip directives we do not recognize
                if (key.endsWith(":") && allowedDirectives.indexOf(key) < 0) {
                    continue;
                }

                String value = (String) entry2.getValue();
                sb.append(";");
                sb.append(key);
                sb.append("=");
                boolean dirty = value.indexOf(',') >= 0 || value.indexOf(';') >= 0;
                if (dirty) {
                    sb.append("\"");
                }
                sb.append(value);
                if (dirty) {
                    sb.append("\"");
                }
            }
            del = ", ";
        }
        return sb.toString();
    }

    /**
     * Parse the XML metadata from the given file.
     * @param metadataFile the metadata file
     */
    private void parseXMLMetadata(File metadataFile) {
      if (metadataFile.isDirectory()) {
        // Traverse the directory and parse all files.
        File[] files = metadataFile.listFiles();
        for (int i = 0; i< files.length; i++) {
          parseXMLMetadata(files[i]);
        }
      } else if (metadataFile.getName().endsWith(".xml")) { // Detect XML by extension,
                                  // others are ignored.
          try {
              InputStream stream = null;
              URL url = metadataFile.toURI().toURL();
              if (url == null) {
                  warn("Cannot find the metadata file : " + metadataFile.getAbsolutePath());
                  m_metadata = new ArrayList/*Element*/();
              } else {
                  stream = url.openStream();
                  parseXMLMetadata(stream); // m_metadata is set by the method.
              }
          } catch (MalformedURLException e) {
              error("Cannot open the metadata input stream from " + metadataFile.getAbsolutePath() + ": " + e.getMessage());
              m_metadata = null;
          } catch (IOException e) {
              error("Cannot open the metadata input stream: " + metadataFile.getAbsolutePath() + ": " + e.getMessage());
              m_metadata = null;
          }

          // m_metadata can be either an empty array or an Element
          // array with component type description. It also can be null
          // if no metadata file is given.
      }
    }

    /**
     * Parses XML Metadata.
     * @param stream metadata input stream.
     */
    private void parseXMLMetadata(InputStream stream) {
        Element[] meta = null;
        try {
            XMLReader parser = XMLReaderFactory.createXMLReader();
            XMLMetadataParser handler = new XMLMetadataParser();
            parser.setContentHandler(handler);
            parser.setFeature("http://xml.org/sax/features/validation",
                    true);
            parser.setFeature("http://apache.org/xml/features/validation/schema",
                    true);

            parser.setErrorHandler(handler);

            if (! m_ignoreLocalXSD) {
                parser.setEntityResolver(new SchemaResolver());
            }

            InputSource is = new InputSource(stream);
            parser.parse(is);
            meta = handler.getMetadata();
            stream.close();

        } catch (IOException e) {
            error("Cannot open the metadata input stream: " + e.getMessage());
        } catch (ParseException e) {
            error("Parsing error when parsing the XML file: " + e.getMessage());
        } catch (SAXParseException e) {
            error("Error during metadata parsing at line " + e.getLineNumber() + " : " + e.getMessage());
        } catch (SAXException e) {
            error("Parsing error when parsing (Sax Error) the XML file: " + e.getMessage());
        }

        if (meta == null || meta.length == 0) {
            warn("Neither component types, nor instances in the XML metadata");
        }

        m_metadata.addAll(Arrays.asList(meta));
    }

    /**
     * Get packages referenced by component.
     * @return the list of referenced packages.
     */
    private List getReferredPackages() {
        List referred = new ArrayList();
        for (int i = 0; i < m_metadata.size(); i++) {
            Element[] elems = ((Element) m_metadata.get(i)).getElements();
            for (int j = 0; j < elems.length; j++) {
                String att = elems[j].getAttribute("specification");
                if (att != null) {
                    int last = att.lastIndexOf('.');
                    if (last != -1) {
                        referred.add(att.substring(0, last));
                    }
                }
            }
        }
        return referred;
    }

    /**
     * Generate manipulation metadata.
     * @param element : actual element.
     * @param actual : actual manipulation metadata.
     * @return : given manipulation metadata + manipulation metadata of the given element.
     */
    private StringBuffer buildManifestMetadata(Element element, StringBuffer actual) {
      // If the element is already here, do not re-add the element.
        if(isInjectedElement(element)) {
            return actual;
        }

        StringBuffer result = new StringBuffer();
        if (element.getNameSpace() == null) {
            result.append(actual + element.getName() + " { ");
        } else {
            result.append(actual + element.getNameSpace() + ":" + element.getName() + " { ");
        }

        Attribute[] atts = element.getAttributes();
        for (int i = 0; i < atts.length; i++) {
            Attribute current = (Attribute) atts[i];

            if (current.getNameSpace() == null) {
                result.append("$" + current.getName() + "=\"" + current.getValue() + "\" ");
            } else {
                result.append("$" + current.getNameSpace() + ":" + current.getName() + "=\"" + current.getValue() + "\" ");
            }
        }

        Element[] elems = element.getElements();
        for (int i = 0; i < elems.length; i++) {
            result = buildManifestMetadata(elems[i], result);
        }

        result.append("}");
        return result;
    }

    /**
     * Checks if the given element is an iPOJO generated element from a prior treatment
     * @return <code>true</code> if the given element was already injected by iPOJO
     */
    private boolean isInjectedElement(final Element element) {
        Attribute[] atts = element.getAttributes();

        if (m_manifestAttributeFilters == null) {
            return false;
        }

        for (int i = 0; i < atts.length; i++) {

            // First test : iPOJO injected elements filter
            String value = atts[i].getValue();
            if (value.startsWith(MethodCreator.PREFIX)
                || value.contains("org.apache.felix.ipojo.InstanceManager")
                || value.contains("_setInstanceManager")) {
                    return true;
            }

            // Second test : customized filters
            Iterator iterator = m_manifestAttributeFilters.iterator();
            while (iterator.hasNext()) {
                ManifestAttributeFilter filter = (ManifestAttributeFilter) iterator.next();
                if (filter.accept(atts[i])) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Adds a Manifest attribute filter to the list
     * @param filter The new Manifest attribute filter
     */
    public void addManifestAttributeFilters(final ManifestAttributeFilter filter) {
        if (filter != null) {
            m_manifestAttributeFilters.add(filter);
        }
    }

    /**
     * Retrieves an OutputStream to write in the Manifest.
     * @return A valid output stream
     */
    protected void writeManifest(Manifest mf) throws IOException {
        if (m_manifest == null) {
            m_manifest = new File(m_dir, "META-INF/MANIFEST.MF");
            if (! m_manifest.exists()) {
                throw new IOException("Cannot find the manifest file : " + m_manifest.getAbsolutePath());
            }
        } else {
            if (! m_manifest.exists()) {
                throw new IOException("Cannot find the manifest file : " + m_manifest.getAbsolutePath());
            }
        }

        mf.write(new FileOutputStream(m_manifest));
    }

    /**
     * Retrieves an InputStream to read the Manifest.
     * @return A valid input stream
     */
    protected InputStream getManifestInputStream() throws IOException {
        if (m_manifest == null) {
            File manFile = new File(m_dir, "META-INF/MANIFEST.MF");
            if (manFile.exists()) {
                return new FileInputStream(manFile);
            } else {
                throw new IOException("Cannot find the manifest file : " + manFile.getAbsolutePath());
            }
        } else {
            if (m_manifest.exists()) {
                return new FileInputStream(m_manifest);
            } else {
                throw new IOException("Cannot find the manifest file : " + m_manifest.getAbsolutePath());
            }
        }
    }

    /**
     * Writes the .class raw data to the storage support
     * @param classFile - Output .class file
     * @param rawClass - Raw class representation
     * @throws IOException - Something wrong occurred while writing the file
     */
    protected void setBytecode(final File classFile, final byte[] rawClass) throws IOException {
        OutputStream os = new FileOutputStream(classFile);
        os.write(rawClass);
        os.close();
    }

    public List getWarnings() {
        return m_warnings;
    }

}
TOP

Related Classes of org.apache.felix.ipojo.manipulator.Pojoization

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.