blob: 1ce3f02976b88007ba1db3c45cca13cff111da89 [file] [log] [blame]
/*
* 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.core.startup;
import java.beans.Beans;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import org.netbeans.Events;
import org.netbeans.InvalidException;
import org.netbeans.Module;
import org.netbeans.Util;
import org.openide.util.Exceptions;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.ExClipboard;
import org.openide.util.SharedClassObject;
import org.openide.util.Lookup;
// XXX synchronization?
/** Class representing one specially-treated section in a module's manifest file.
* For example, one section may describe a single action provided by the module.
*
* @author Jaroslav Tulach, Jesse Glick
*/
public abstract class ManifestSection<T> {
/** superclass of section either Class or String name of the class*/
private final Object superclazz;
/** name of the class file, e.g. foo/Bar.class, or foo/bar.ser */
private final String name;
/** name of the class, e.g. foo.bar */
private final String className;
/** the class involved */
private Class<?> clazz;
/** instance of the class if possible */
private Object result;
/** any exception associated with loading the object */
private Exception problem;
/** associated module */
private final Module module;
/** Create a manifest section generally.
* @param name name of section, should be a class file, e.g. foo/Bar.class
* @param module associated module
* @param superclazz super-class of instances of this section
* @throws InvalidException if the name is not valid for an OpenIDE section
* (exception must include module for reference)
*/
protected ManifestSection(String name, Module module, Object superclazz) throws InvalidException {
this.name = name;
this.module = module;
this.superclazz = superclazz;
try {
className = Util.createPackageName(name);
} catch (IllegalArgumentException iae) {
InvalidException ie = new InvalidException(module, iae.toString());
ie.initCause(iae);
throw ie;
}
}
/** Get the associated module. */
public final Module getModule() {
return module;
}
/** Get the classloader used to load this section. */
protected final ClassLoader getClassLoader() {
return module != null ? module.getClassLoader() : getClass().getClassLoader();
}
/** Does this section represent a default instance?
* Normally true, but false when deserializing beans.
*/
public final boolean isDefaultInstance() {
return name.endsWith(".class"); // NOI18N
}
/** Get the class which the generated instances will have.
* @return the class
* @throws Exception for various reasons
*/
public final Class<?> getSectionClass() throws Exception {
if (clazz != null) {
return clazz;
}
if (problem != null) {
throw problem;
}
if (isDefaultInstance()) {
try {
clazz = getClassLoader().loadClass(className);
if (! getSuperclass().isAssignableFrom(clazz)) {
throw new ClassCastException("Class " + clazz.getName() + " is not a subclass of " + getSuperclass().getName()); // NOI18N
}
// Don't try to check .ser files: it is quite legitimate to
// serialize in a module objects whose class is from elsewhere
// (e.g. the core).
if (clazz.getClassLoader() != getClassLoader()) { // NOI18N
Events ev = module.getManager().getEvents();
ev.log(Events.WRONG_CLASS_LOADER, module, clazz, getClassLoader());
}
return clazz;
} catch (ClassNotFoundException cnfe) {
Exceptions.attachMessage(cnfe,
"Loader for ClassNotFoundException: " +
getClassLoader());
problem = cnfe;
throw problem;
} catch (Exception e) {
problem = e;
throw problem;
} catch (LinkageError t) {
problem = new ClassNotFoundException(t.toString(), t);
throw problem;
}
} else {
return (clazz = getInstance().getClass());
}
}
/** Same as {@link #getSectionClass}, but only provides the name of the class.
* Could be more efficient because it will not try to load the class unless
* a serialized bean is in use.
*/
public String getSectionClassName() throws Exception {
if (isDefaultInstance()) {
return className;
} else {
return getSectionClass().getName();
}
}
/** Create a fresh instance.
* @return the instance
* @exception Exception if there is an error
*/
protected final Object createInstance() throws Exception {
if (! isDefaultInstance()) {
try {
Object o = Beans.instantiate(getClassLoader(), className);
clazz = o.getClass();
if (! getSectionClass().isAssignableFrom(clazz)) {
throw new ClassCastException("Class " + clazz.getName() + " is not a subclass of " + getSuperclass().getName()); // NOI18N
}
return o;
} catch (ClassNotFoundException cnfe) {
Exceptions.attachMessage(cnfe,
"Loader for ClassNotFoundException: " +
getClassLoader());
throw cnfe;
} catch (LinkageError le) {
throw new ClassNotFoundException(le.toString(), le);
}
} else {
getSectionClass(); // might throw some exceptions
if (SharedClassObject.class.isAssignableFrom(clazz)) {
return SharedClassObject.findObject(clazz.asSubclass(SharedClassObject.class), true);
} else {
return clazz.newInstance();
}
}
}
/** Get a single cached instance.
* @return the instance
* @exception Exception if there is an error
*/
public final Object getInstance() throws Exception {
if (problem != null) {
problem.fillInStackTrace(); // XXX is this a good idea?
throw problem;
}
if (result == null) {
try {
result = createInstance();
} catch (Exception ex) {
// remember the exception
problem = ex;
throw problem;
} catch (LinkageError t) {
problem = new ClassNotFoundException(t.toString(), t);
throw problem;
}
}
return result;
}
/** Get the superclass which all instances of this section are expected to
* be assignable to.
*/
public final Class<?> getSuperclass() {
if (superclazz instanceof Class) {
return (Class)superclazz;
} else {
try {
return getClazz ((String)superclazz, module);
} catch (InvalidException ex) {
throw (IllegalStateException)new IllegalStateException (superclazz.toString()).initCause (ex);
}
}
}
/** Dispose of a section. Used when a module will be uninstalled and all its
* resources should be released.
*/
public void dispose() {
result = null;
problem = null;
clazz = null;
}
/** String representation for debugging. */
public String toString() {
return "ManifestSection[" + className + "]"; // NOI18N
}
/** Parse a manifest section and make an object representation of it.
* @param name name of the section (i.e. file to load)
* @param attr attributes of the manifest section
* @param module the associated module
* @return the section or null if this manifest section is not related to module installation
* @exception InvalidException if the attributes are not valid
*/
public static ManifestSection create(String name, Attributes attr, Module module) throws InvalidException {
String sectionName = attr.getValue("OpenIDE-Module-Class"); // NOI18N
if (sectionName == null) {
// no section tag
return null;
} else if (sectionName.equalsIgnoreCase("Action")) { // NOI18N
warnObsolete(sectionName, module);
return new ActionSection(name, module);
} else if (sectionName.equalsIgnoreCase("Option")) { // NOI18N
warnObsolete(sectionName, module);
return null;
} else if (sectionName.equalsIgnoreCase("Loader")) { // NOI18N
warnObsolete(sectionName, module);
return new LoaderSection(name, attr, module);
} else if (sectionName.equalsIgnoreCase("Filesystem")) { // NOI18N
warnObsolete(sectionName, module);
return null;
} else if (sectionName.equalsIgnoreCase("Node")) { // NOI18N
warnObsolete(sectionName, module);
Util.err.warning("(See http://www.netbeans.org/issues/show_bug.cgi?id=19609, last comment, for howto.)");
return null;
} else if (sectionName.equalsIgnoreCase("Service")) { // NOI18N
warnObsolete(sectionName, module);
return null;
} else if (sectionName.equalsIgnoreCase("Debugger")) { // NOI18N
// XXX should support for this be dropped entirely?
warnObsolete(sectionName, module);
return new DebuggerSection(name, module);
} else if (sectionName.equalsIgnoreCase("ClipboardConvertor")) { // NOI18N
// XXX should support for this be dropped entirely?
warnObsolete(sectionName, module);
return new ClipboardConvertorSection(name, module);
} else {
throw new InvalidException(module, "Illegal manifest section type: " + sectionName); // NOI18N
}
}
private static void warnObsolete(String sectionName, Module module) {
if (module == null) {
return; // NbLoaderPoolResolverChangeTest
}
Util.err.warning("Use of OpenIDE-Module-Class: " + sectionName + " in " + module.getCodeNameBase() + " is obsolete.");
Util.err.warning("(Please use layer-based installation of objects instead.)");
}
/** Module section for an Action.
* @see SystemAction
*/
public static final class ActionSection extends ManifestSection {
ActionSection(String name, Module module) throws InvalidException {
super(name, module, SystemAction.class);
}
}
/** Module section for a Data Loader.
*/
public static final class LoaderSection extends ManifestSection {
/** class name(s) of data object to
* be inserted after loader that recognizes its
*/
private final String[] installAfter;
/** class name(s) of data object to be inserted before its recognizing
* data loader
*/
private final String[] installBefore;
LoaderSection(String name, Attributes attrs, Module module) throws InvalidException {
super (name, module, "org.openide.loaders.DataLoader"); // NOI18N
String val = attrs.getValue("Install-After"); // NOI18N
StringTokenizer tok;
List<String> res;
// XXX validate classnames etc.
if (val != null) {
tok = new StringTokenizer(val, ", "); // NOI18N
res = new LinkedList<String>();
while (tok.hasMoreTokens()) {
String clazz = tok.nextToken();
if (! clazz.equals("")) // NOI18N
res.add(clazz);
}
installAfter = res.toArray(new String[res.size()]);
} else {
installAfter = null;
}
val = attrs.getValue("Install-Before"); // NOI18N
if (val != null) {
tok = new StringTokenizer(val, ", "); // NOI18N
res = new LinkedList<String>();
while (tok.hasMoreTokens()) {
String clazz = tok.nextToken();
if (! clazz.equals("")) // NOI18N
res.add(clazz);
}
installBefore = res.toArray(new String[res.size()]);
} else {
installBefore = null;
}
}
/** Get the representation class(es) of the loader(s) that this one should be installed after.
* @return a list of class names, or <code>null</code>
*/
public String[] getInstallAfter() {
return installAfter;
}
/** Get the representation class(es) of the loader(s) that this one should be installed before.
* @return a list of class names, or <code>null</code>
*/
public String[] getInstallBefore() {
return installBefore;
}
}
/** @deprecated use new debugger API
*/
@Deprecated
public static final class DebuggerSection extends ManifestSection {
DebuggerSection(String name, Module module) throws InvalidException {
super(name, module, getClazz("org.openide.debugger.Debugger", module)); // NOI18N
}
}
/** @deprecated use META-INF/services to register convertors.
*/
@Deprecated
public static final class ClipboardConvertorSection extends ManifestSection {
ClipboardConvertorSection(String name, Module module) throws InvalidException {
super(name, module, ExClipboard.Convertor.class);
}
}
/** Loads class of given name.
*/
static Class<?> getClazz(String name, Module m) throws InvalidException {
try {
return Lookup.getDefault().lookup(ClassLoader.class).loadClass(name);
} catch (ClassNotFoundException cnfe) {
InvalidException e = new InvalidException(m, "Unable to locate class: " + name + " maybe you do not have its module enabled!?"); // NOI18N
e.initCause(cnfe);
throw e;
}
}
}