All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.upgrade.systemoptions.SettingsRecognizer Maven / Gradle / Ivy

The newest version!
/*
 * 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.netbeans.upgrade.systemoptions;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
//import org.openide.modules.SpecificationVersion;
import org.openide.util.Lookup;
import org.openide.util.SharedClassObject;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * Copy of XMLSettingsSupport.SettingsRecognizer by Jan Pokorsky
 */
public class SettingsRecognizer  extends org.xml.sax.helpers.DefaultHandler {
    public static final String INSTANCE_DTD_ID = "-//NetBeans//DTD Session settings 1.0//EN"; // NOI18N
    static final ErrorManager err = ErrorManager.getDefault().getInstance(SettingsRecognizer.class.getName()); // NOI18N
    
    private static final String ELM_SETTING = "settings"; // NOI18N
    private static final String ATR_SETTING_VERSION = "version"; // NOI18N
    
    private static final String ELM_MODULE = "module"; // NOI18N
    private static final String ATR_MODULE_NAME = "name"; // NOI18N
    private static final String ATR_MODULE_SPEC = "spec"; // NOI18N
    private static final String ATR_MODULE_IMPL = "impl"; // NOI18N
    
    private static final String ELM_INSTANCE = "instance"; // NOI18N
    private static final String ATR_INSTANCE_CLASS = "class"; // NOI18N
    private static final String ATR_INSTANCE_METHOD = "method"; // NOI18N
    
    private static final String ELM_INSTANCEOF = "instanceof"; // NOI18N
    private static final String ATR_INSTANCEOF_CLASS = "class"; // NOI18N
    
    private static final String ELM_SERIALDATA = "serialdata"; // NOI18N
    private static final String ATR_SERIALDATA_CLASS = "class"; // NOI18N
    
    //private static final String VERSION = "1.0"; // NOI18N
    
    private boolean header;
    private Deque stack;
    
    private String version;
    private String instanceClass;
    private String instanceMethod;
    private Set instanceOf = new HashSet<>();
    
    private byte[] serialdata;
    private CharArrayWriter chaos = null;
    
    private String codeName;
    private String codeNameBase;
    private int codeNameRelease;
    //private SpecificationVersion moduleSpec;
    private String moduleImpl;
    /** file with stored settings */
    private final FileObject source;
    
    /** XML handler recognizing settings.
     * @param header if true read just elements instanceof, module and attr classname.
     * @param source file with stored settings
     */
    public SettingsRecognizer(boolean header, FileObject source) {
        this.header = header;
        this.source = source;
    }
    
    public boolean isAllRead() {
        return !header;
    }
    
    public void setAllRead(boolean all) {
        if (!header) return;
        header = all;
    }
    
    public String getSettingsVerison() {
        return version;
    }
    
    public String getCodeName() {
        return codeName;
    }
    
    public String getCodeNameBase() {
        return codeNameBase;
    }
    
    public int getCodeNameRelease() {
        return codeNameRelease;
    }
    
    /*public SpecificationVersion getSpecificationVersion() {
        return moduleSpec;
    }*/
    
    public String getModuleImpl() {
        return moduleImpl;
    }
    
    /** Set of names. */
    public Set getInstanceOf() {
        return instanceOf;
    }
    
    /** Method attribute from the instance element. */
    public String getMethodName() {
        return instanceMethod;
    }
    
    /** Serialized instance, can be null. */
    public InputStream getSerializedInstance() {
        if (serialdata == null) return null;
        return new ByteArrayInputStream(serialdata);
    }
    
    @Override
    public org.xml.sax.InputSource resolveEntity(String publicId, String systemId)
    throws SAXException {
        if (INSTANCE_DTD_ID.equals(publicId)) {
            return new org.xml.sax.InputSource(new ByteArrayInputStream(new byte[0]));
        } else {
            return null; // i.e. follow advice of systemID
        }
    }
    
    @Override
    public void characters(char[] values, int start, int length) throws SAXException {
        if (header) return;
        String element = stack.peek();
        if (ELM_SERIALDATA.equals(element)) {
            // [PENDING] should be optimized to do not read all chars to memory
            if (chaos == null) chaos = new CharArrayWriter(length);
            chaos.write(values, start, length);
        }
    }
    
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
        stack.push(qName);
        if (ELM_SETTING.equals(qName)) {
            version = attribs.getValue(ATR_SETTING_VERSION);
        } else if (ELM_MODULE.equals(qName)) {
            codeName = attribs.getValue(ATR_MODULE_NAME);
            resolveModuleElm(codeName);
            moduleImpl = attribs.getValue(ATR_MODULE_IMPL);
            try {
                String spec = attribs.getValue(ATR_MODULE_SPEC);
                //moduleSpec = spec == null ? null : new SpecificationVersion(spec);
            } catch (NumberFormatException nfe) {
                throw new SAXException(nfe);
            }
        } else if (ELM_INSTANCEOF.equals(qName)) {
            instanceOf.add(org.openide.util.Utilities.translate(
                    attribs.getValue(ATR_INSTANCEOF_CLASS)));
        } else if (ELM_INSTANCE.equals(qName)) {
            instanceClass = attribs.getValue(ATR_INSTANCE_CLASS);
            if (instanceClass == null) {
                System.err.println("Hint: NPE is caused by broken settings file: " + source ); // NOI18N
            }
            instanceClass = org.openide.util.Utilities.translate(instanceClass);
            instanceMethod = attribs.getValue(ATR_INSTANCE_METHOD);
        } else if (ELM_SERIALDATA.equals(qName)) {
            instanceClass = attribs.getValue(ATR_SERIALDATA_CLASS);
            instanceClass = org.openide.util.Utilities.translate(instanceClass);
            if (header) throw new StopSAXException();
        }
    }
    
    /** reade codenamebase + revision */
    private void resolveModuleElm(String codeName) {
        if (codeName != null) {
            int slash = codeName.indexOf("/"); // NOI18N
            if (slash == -1) {
                codeNameBase = codeName;
                codeNameRelease = -1;
            } else {
                codeNameBase = codeName.substring(0, slash);
                try {
                    codeNameRelease = Integer.parseInt(codeName.substring(slash + 1));
                } catch (NumberFormatException ex) {
                    ErrorManager emgr = ErrorManager.getDefault();
                    emgr.annotate(ex, "Content: \n" + getFileContent(source)); // NOI18N
                    emgr.annotate(ex, "Source: " + source); // NOI18N
                    emgr.notify(ErrorManager.INFORMATIONAL, ex);
                    codeNameRelease = -1;
                }
            }
        } else {
            codeNameBase = null;
            codeNameRelease = -1;
        }
    }
    
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        //if (header) return;
        String element = stack.pop();
        if (ELM_SERIALDATA.equals(element)) {
            if (chaos != null) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(chaos.size() >> 1);
                try {
                    chars2Bytes(baos, chaos.toCharArray(), 0, chaos.size());
                    serialdata = baos.toByteArray();
                } catch (IOException ex) {
                    ErrorManager.getDefault().notify(
                            ErrorManager.WARNING, ex
                            );
                } finally {
                    chaos = null; // don't keep the info twice
                    try {
                        baos.close();
                    } catch (IOException ex) {
                        // doesn't matter
                    }
                }
            }
        }
    }
    
    /** Tries to deserialize instance saved in is.
     * @param is    stream with stored object, can be null
     * @return deserialized object or null
     */
    private Object readSerial(InputStream is) throws IOException, ClassNotFoundException {
        if (is == null) return null;
        try {
            try (ObjectInput oi = new ObjectInputStream(is)) {
                Object o = oi.readObject();
                return o;
            }
        } catch (IOException ex) {
            ErrorManager emgr = ErrorManager.getDefault();
            emgr.annotate(ex, "Content: \n" + getFileContent(source)); // NOI18N
            emgr.annotate(ex, "Source: " + source); // NOI18N
            emgr.annotate(ex, "Cannot read class: " + instanceClass); // NOI18N
            throw ex;
        } catch (ClassNotFoundException ex) {
            ErrorManager emgr = ErrorManager.getDefault();
            emgr.annotate(ex, "Content: \n" + getFileContent(source)); // NOI18N
            emgr.annotate(ex, "Source: " + source); // NOI18N
            throw ex;
        }
    }
    
    /** Create an instance.
     * @return the instance of type {@link #instanceClass}
     * @exception IOException if an I/O error occured
     * @exception ClassNotFoundException if a class was not found
     */
    public Object instanceCreate() throws java.io.IOException, ClassNotFoundException {
        Object inst = null;
        
        // deserialize
        inst = readSerial(getSerializedInstance());
        
        // default instance
        if (inst == null) {
            if (instanceMethod != null) {
                inst = createFromMethod(instanceClass, instanceMethod);
            } else {
                // use default constructor
                Class clazz = instanceClass();
                if (SharedClassObject.class.isAssignableFrom(clazz)) {
                    inst = SharedClassObject.findObject(clazz.asSubclass(SharedClassObject.class), false);
                    if (null != inst) {
                        // instance already exists -> reset it to defaults
                        try {
                            Method method = SharedClassObject.class.getDeclaredMethod("reset", new Class[0]); // NOI18N
                            method.setAccessible(true);
                            method.invoke(inst, new Object[0]);
                        } catch (Exception e) {
                            ErrorManager.getDefault().notify(e);
                        }
                    } else {
                        inst = SharedClassObject.findObject(clazz.asSubclass(SharedClassObject.class), true);
                    }
                } else {
                    try {
                        inst = clazz.getDeclaredConstructor().newInstance();
                    } catch (Exception ex) {
                        IOException ioe = new IOException();
                        ErrorManager emgr = ErrorManager.getDefault();
                        emgr.annotate(ioe, ex);
                        emgr.annotate(ioe, "Content: \n" + getFileContent(source)); // NOI18N
                        emgr.annotate(ioe, "Class: " + clazz); // NOI18N
                        emgr.annotate(ioe, "Source: " + source); // NOI18N
                        throw ioe;
                    }
                }
            }
        }
        
        return inst;
    }
    
    /** Get file content as String. If some exception occures its stack trace
     * is return instead. */
    private static String getFileContent(FileObject fo) {
        try {
            InputStreamReader isr = new InputStreamReader(fo.getInputStream());
            char[] cbuf = new char[1024];
            int length;
            StringBuilder sbuf = new StringBuilder(1024);
            while (true) {
                length = isr.read(cbuf);
                if (length > 0) {
                    sbuf.append(cbuf, 0, length);
                } else {
                    return sbuf.toString();
                }
            }
        } catch (Exception ex) {
            StringWriter sw = new StringWriter();
            ex.printStackTrace(new PrintWriter(sw));
            return sw.toString();
        }
    }
    
    /** create instance by invoking class method */
    private Object createFromMethod(String srcClazz, String srcMethod)
    throws ClassNotFoundException, IOException {
        int dotIndex = instanceMethod.lastIndexOf('.');
        String targetClass;
        String targetMethod;
        if (dotIndex > 0) {
            targetClass = srcMethod.substring(0, dotIndex);
            targetMethod = srcMethod.substring(dotIndex + 1);
        } else {
            targetClass = srcClazz;
            targetMethod = srcMethod;
        }
        
        Class clazz = loadClass(targetClass);
        
        try {
            Object instance;
            try {
                Method method = clazz.getMethod(targetMethod, new Class[]{FileObject.class});
                method.setAccessible(true);
                instance = method.invoke(null, source);
            } catch (NoSuchMethodException ex) {
                Method method = clazz.getMethod(targetMethod);
                method.setAccessible(true);
                instance = method.invoke(null, new Object[0]);
            }
            if (instance == null) {
                // Strictly verboten. Cf. BT #4827173 for example.
                throw new IOException("Null return not permitted from " + targetClass + "." + targetMethod); // NOI18N
            }
            return instance;
        } catch (Exception ex) {
            IOException ioe = new IOException("Error reading " + source + ": " + ex); // NOI18N
            ErrorManager emgr = ErrorManager.getDefault();
            emgr.annotate(ioe, "Class: " + clazz);  // NOI18N
            emgr.annotate(ioe, "Method: " + srcMethod);  // NOI18N
            emgr.annotate(ioe, ex);
            emgr.annotate(ioe, "Content:\n" + getFileContent(source)); // NOI18N
            throw ioe;
        }
    }
    
    /** The representation type that may be created as instances.
     * Can be used to test whether the instance is of an appropriate
     * class without actually creating it.
     *
     * @return the representation class of the instance
     * @exception IOException if an I/O error occurred
     * @exception ClassNotFoundException if a class was not found
     */
    public Class instanceClass() throws java.io.IOException, ClassNotFoundException {
        if (instanceClass == null) {
            throw new ClassNotFoundException(source +
                    ": missing 'class' attribute in 'instance' element"); //NOI18N
        }
        
        return loadClass(instanceClass);
    }
    
    /** try to load class from system and current classloader. */
    private Class loadClass(String clazz) throws ClassNotFoundException {
        return ((ClassLoader)Lookup.getDefault().lookup(ClassLoader.class)).loadClass(clazz);
    }
    
    /** get class name of instance */
    public String instanceName() {
        if (instanceClass == null) {
            return ""; // NOI18N
        } else {
            return instanceClass;
        }
    }
    
    private int tr(char c) {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        return -1;
    }
    
    /** Converts array of chars to array of bytes. All whitespaces and
     * unknown chars are skipped.
     */
    private void chars2Bytes(OutputStream os, char[] chars, int off, int length)
    throws IOException {
        byte rbyte;
        int read;
        
        for (int i = off; i < length; ) {
            read = tr(chars[i++]);
            if (read >= 0) rbyte = (byte) (read << 4); // * 16;
            else continue;
            
            while (i < length) {
                read = tr(chars[i++]);
                if (read >= 0) {
                    rbyte += (byte) read;
                    os.write(rbyte);
                    break;
                }
            }
        }
    }
    
    /** Parse settings file. */
    public void parse() throws IOException {
        InputStream in = null;
        
        try {
            if (header) {
                if (err.isLoggable(err.INFORMATIONAL) && source.getSize() < 12000) {
                    // log the content of the stream
                    byte[] arr = new byte[(int)source.getSize()];
                    try (InputStream temp = source.getInputStream()) {
                        int len = temp.read(arr);
                        if (len != arr.length) {
                            throw new IOException("Could not read " + arr.length + " bytes from " + source + " just " + len); // NOI18N
                        }
                        
                        err.log("Parsing:" + new String(arr));
                    }
                    
                    in = new ByteArrayInputStream(arr);
                } else {
                    in = new BufferedInputStream(source.getInputStream());
                }
                Set iofs = quickParse(new BufferedInputStream(in));
                if (iofs != null) {
                    instanceOf = iofs;
                    return;
                }
            }
        } catch (IOException ioe) {
            // ignore - fallback to XML parser follows
        } finally {
            if (in != null) in.close();
        }
        stack = new ArrayDeque<>();
        try {
            in = source.getInputStream();
            XMLReader reader = org.openide.xml.XMLUtil.createXMLReader();
            reader.setContentHandler(this);
            reader.setErrorHandler(this);
            reader.setEntityResolver(this);
            reader.parse(new org.xml.sax.InputSource(new BufferedInputStream(in)));
        } catch (SettingsRecognizer.StopSAXException ex) {
            // Ok, header is read
        } catch (SAXException ex) {
            IOException ioe = new IOException(source.toString()); // NOI18N
            ErrorManager emgr = ErrorManager.getDefault();
            emgr.annotate(ioe, ex);
            if (ex.getException() != null) {
                emgr.annotate(ioe, ex.getException());
            }
            emgr.annotate(ioe, "Content: \n" + getFileContent(source)); // NOI18N
            emgr.annotate(ioe, "Source: " + source); // NOI18N
            throw ioe;
        } finally {
            stack = null;
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                // ignore already closed
            }
        }
    }
    
    /** Parse setting from source. */
    public void parse(Reader source) throws IOException {
        stack = new ArrayDeque<>();
        
        try {
            XMLReader reader = org.openide.xml.XMLUtil.createXMLReader();
            reader.setContentHandler(this);
            reader.setErrorHandler(this);
            reader.setEntityResolver(this);
            reader.parse(new org.xml.sax.InputSource(source));
        } catch (SettingsRecognizer.StopSAXException ex) {
            // Ok, header is read
        } catch (SAXException ex) {
            IOException ioe = new IOException(source.toString()); // NOI18N
            ErrorManager emgr = ErrorManager.getDefault();
            emgr.annotate(ioe, ex);
            if (ex.getException() != null) {
                emgr.annotate(ioe, ex.getException());
            }
            throw ioe;
        } finally {
            stack = null;
        }
    }
    
    // Encoding irrelevant for these getBytes() calls: all are ASCII...
    // (unless someone has their system encoding set to UCS-16!)
    private static final byte[] MODULE_SETTINGS_INTRO = "   quickParse(InputStream is) throws IOException {
        Set iofs = new HashSet<>();
        
        if (!expect(is, MODULE_SETTINGS_INTRO)) {
            err.log("Could not read intro "+source); // NOI18N
            return null;
        }
        version = readTo(is, '"');
        if (version == null) {
            err.log("Could not read version "+source); // NOI18N
            return null;
        }
        if (!expect(is, MODULE_SETTINGS_INTRO_END)) {
            err.log("Could not read stuff after cnb "+source); // NOI18N
            return null;
        }
        // Now we have (module?, instanceof*, (instance | serialdata)).
        int c;
        PARSE:
            while (true) {
                c = is.read();
                switch (c) {
                    case 'm':
                        // 
                        if (!expect(is, MODULE_SETTINGS_MODULE_NAME)) {
                            err.log("Could not read up to 
                        if (!expect(is, MODULE_SETTINGS_MODULE_SPEC)) {
                            err.log("Could not read up to spec=\" "+source); // NOI18N
                            return null;
                        }
                        String mspec = readTo(is, '"');
                        if (mspec == null) {
                            err.log("Could not read module spec value "+source); // NOI18N
                            return null;
                        }
                        try {
                            //moduleSpec = new SpecificationVersion(mspec);
                        } catch (NumberFormatException nfe) {
                            return null;
                        }
                        c = is.read();
                        if (c == '/') {
                            if (!expect(is, MODULE_SETTINGS_TAG_END)) {
                                err.log("Could not read up to end of  tag "+source); // NOI18N
                                return null;
                            }
                            break;
                        } else if (c != ' ') {
                            err.log("Could not read space after module name "+source); // NOI18N
                            return null;
                        }
                        // 
                        if (!expect(is, MODULE_SETTINGS_MODULE_IMPL)) {
                            err.log("Could not read up to impl=\" "+source); // NOI18N
                            return null;
                        }
                        moduleImpl = readTo(is, '"');
                        if (moduleImpl == null) {
                            err.log("Could not read module impl value "+source); // NOI18N
                            return null;
                        }
                        moduleImpl = moduleImpl.intern();
                        // /> >
                        if (!expect(is, MODULE_SETTINGS_TAG_END)) {
                            err.log("Could not read up to /> < "+source); // NOI18N
                            return null;
                        }
                        break;
                    case 'i':
                        //  or 
                        if (!expect(is, MODULE_SETTINGS_INSTANCE)) {
                            err.log("Could not read up to instance "+source); // NOI18N
                            return null;
                        }
                        // Now we need to check which one
                        c = is.read();
                        if (c == 'o') {
                            if (!expect(is, MODULE_SETTINGS_OF)) {
                                err.log("Could not read up to instance"); // NOI18N
                                return null;
                            }
                            String iof = readTo(is, '"');
                            if (iof == null) {
                                err.log("Could not read instanceof value "+source); // NOI18N
                                return null;
                            }
                            iof = org.openide.util.Utilities.translate(iof).intern();
                            iofs.add(iof);
                            if (is.read() != '/') {
                                err.log("No / at end of  " + iof+" "+source); // NOI18N
                                return null;
                            }
                            if (!expect(is, MODULE_SETTINGS_TAG_END)) {
                                err.log("Could not read up to next tag after  " + iof+" "+source); // NOI18N
                                return null;
                            }
                        } else if (c == ' ') {
                            // read class and optional method
                            if (!expect(is, MODULE_SETTINGS_INSTANCE_CLZ)) {
                                err.log("Could not read up to class=\" "+source); // NOI18N
                                return null;
                            }
                            instanceClass = readTo(is, '"');
                            if (instanceClass == null) {
                                err.log("Could not read instance class value "+source); // NOI18N
                                return null;
                            }
                            instanceClass = org.openide.util.Utilities.translate(instanceClass).intern();
                            c = is.read();
                            if (c == '/') {
                                if (!expect(is, MODULE_SETTINGS_TAG_END)) {
                                    err.log("Could not read up to end of instance tag "+source); // NOI18N
                                    return null;
                                }
                                break;
                            } else if (c != ' ') {
                                err.log("Could not space after instance class "+source); // NOI18N
                                return null;
                            }
                            // 
                            if (!expect(is, MODULE_SETTINGS_INSTANCE_MTD)) {
                                err.log("Could not read up to method=\" "+source); // NOI18N
                                return null;
                            }
                            instanceMethod = readTo(is, '"');
                            if (instanceMethod == null) {
                                err.log("Could not read method value "+source); // NOI18N
                                return null;
                            }
                            instanceMethod = instanceMethod.intern();
                            c = is.read();
                            if (c == '/') {
                                if (!expect(is, MODULE_SETTINGS_TAG_END)) {
                                    err.log("Could not read up to end of instance tag "+source); // NOI18N
                                    return null;
                                }
                                break;
                            }
                            err.log("Strange stuff after method attribute "+source); // NOI18N
                            return null;
                        } else {
                            err.log("Could not read after to instance "+source); // NOI18N
                            return null;
                        }
                        break;
                    case 's':
                        //  126) return null;
            if (c == 10 || c == 13) {
                // Normalize: s/[\r\n]+/\n/g
                if (inNewline) {
                    continue;
                } else {
                    inNewline = true;
                    c = 10;
                }
            } else if (c < 32 && c != 9) {
                // Random control character!
                return null;
            } else {
                inNewline = false;
            }
            if (c == delim) {
                return caw.toString();
            } else {
                caw.write(c);
            }
        }
    }
    
    static final class StopSAXException extends SAXException {
        public StopSAXException() {
            super("Parser stopped"); // NOI18N
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy