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':
// ') {
err.log("Could not read up to end of serialdata tag "+source); // NOI18N
return null;
}
break PARSE;
case '/':
// 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
}
}
}