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

net.jini.loader.pref.PreferredResources 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 net.jini.loader.pref;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * Internal data structure which holds preference information for a
 * preferred class loader.  This utility is used only by the preferred
 * class loader provider and is not intended to be a public API.
 *
 * A preferred resources object is created from an input stream which
 * is formatted according to the Preferred List Syntax which is
 * defined in the specification for
 * net.jini.loader.pref.PreferredClassProvider
 *
 * Preferred resources instances hold preferred list expression data
 * and the preferred state for the resources contained in a given
 * preferred class loader.
 * 
 * PreferredResouces is thread safe.  
 * Originally from package org.apache.river.loader.pref.internal.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
final class PreferredResources {
    /**
     * Constant value that indicates that there is no preference value
     * for a given name.
     */
    public static final int NAME_NO_PREFERENCE = 0;

    /**
     * Constant value that indicates that the resource for a given
     * name is known to be not preferred.  This state is applicable
     * when a preference expression declared that the name is not
     * preferred or when the resource for the name (preferred or not)
     * is known to not exist.
     */
    public static final int NAME_NOT_PREFERRED = 1;

    /**
     * Constant value that indicates that a given name is preferred.
     * This state is applicable when a preference expression declares
     * the name to be preferred but it is uncertain whether the
     * resource for the name the exists.
     */
    public static final int NAME_PREFERRED = 2;

    /**
     * Constant value that indicates that a given resource is
     * preferred.  This state is applicable when a preference
     * expression declares the name to be preferred and the resource
     * for the name is known to exist.
     */
    public static final int NAME_PREFERRED_RESOURCE_EXISTS = 3;
    
    /** string that starts preference specification headers */
    private static final String HEADER_TITLE = "PreferredResources-Version: ";
    private static final String HEADER_MAJOR_VERSION = "1";
    private static final String HEADER_MINOR_VERSION = "0";
    
    /** preference syntax keywords */
    private static final String PREF_PREFIX = "Preferred:";
    private static final String NAME_PREFIX = "Name:";

    /* tables which contain relevant components of preferred names */
    private final Map namespacePrefs;
    private final Map packagePrefs;
    private final Map completeNamePrefs;

    /** flag to signal if this preference object is empty */
    private volatile boolean isEmpty = true;

    /** default preference setting */
    private final Boolean defaultPreference;

    /**
     * Create a preference object from a stream of formatted
     * preference syntax.
     *
     * @see PreferredResources
     */
    public PreferredResources(InputStream in) throws IOException {
        /*
         * Read and parse preference information from the parameter input
         * stream in.  When complete, the
         * preference expression maps contain preference settings for
         * preference names contained in the input stream.
         */
        BufferedReader br = new BufferedReader
	    (new InputStreamReader(in, "UTF8"));	
        String line;
	String name = null;
	Boolean preference = null;
        Boolean defaultPreference = Boolean.FALSE;
        
	completeNamePrefs = new HashMap(53);
	packagePrefs = new HashMap(23);
	namespacePrefs = new HashMap(11);
	
	if ((line = readLineTrimComments(br)) != null) {
	    if (!line.startsWith(HEADER_TITLE)) {
		throw new IOException("unsupported preferred list header: " +
				      line);
	    } else {
		String version =
		    line.substring(HEADER_TITLE.length(),
				   line.length()).trim();
		if (!version.startsWith("1.")) {
		    throw new IOException("preferred list major version " +
					  "not supported");
		}
	    }
	    for (line = readLineTrimComments(br); line != null;
		 line = readLineTrimComments(br))
	    {
		if (line.length() == 0) {
		    continue;
		} else if (line.startsWith(NAME_PREFIX)) {
		    if (name != null) {
			throw new IOException("Preferred name without " +
					      "preference value");
		    }
		    name = line.substring(NAME_PREFIX.length()).trim();
		    continue;
		    
		} else if (line.startsWith(PREF_PREFIX)) {
		    String value = line.substring(PREF_PREFIX.length()).trim();
		    if (value.equals("")) {
			throw new IOException("Empty preference value not " +
					      "permitted");
		    }
		    preference = Boolean.valueOf(value);
		} else {
		    throw new IOException("unrecognized preference entry: " +
					  line);
		}
		if (name == null) {
		    if (preference != null) {
			if (!isEmpty) {
			    throw new IOException("default preference must " +
				"be the first expression and can not " +
				"be redefined");
			}
			defaultPreference = preference;
			preference = null;
			isEmpty = false;
		    }
		    
		    /* it is ok for both name and preference to be null */
		    
		} else if (preference != null) {
		    if (name.startsWith("/") ||
			name.startsWith("*") ||
			name.startsWith("-") ||			
			name.startsWith("."))
		    {
			throw new IOException("Invalid character " +
					      "at name beginning: " + name);
		    } else if (name.endsWith("/*")) {
			mapPut(packagePrefs,
			    name.substring(0, name.length() - 2), preference);
		    } else if (name.endsWith("/-")) {
			mapPut(namespacePrefs,
			    name.substring(0, name.length() - 2), preference);
		    } else if (name.endsWith("/")) {
			mapPut(packagePrefs,
			    name.substring(0, name.length() - 1), preference);
		    } else {
			/* no wildcard; must be a full resource name */
			int state = (preference.booleanValue() ?
				     NAME_PREFERRED : NAME_NOT_PREFERRED);
			mapPut(completeNamePrefs, name, Integer.valueOf(state));
		    }
		    preference = null;
		    name = null;
		}
	    }
	    if (name != null) {
		throw new IOException("Preferred name without " +
				      "preference value");
	    }
	}
	if (isEmpty) {
	    throw new IOException("Empty preferences list is invalid");
	}
        this.defaultPreference = defaultPreference;
    }

    /**
     * Reads the next line from the specified BufferedReader, removing
     * leading and trailing whitespace and comments.  Null is returned
     * on EOF.
     **/
    private String readLineTrimComments(BufferedReader br) throws IOException {
	String line = br.readLine();
	if (line != null) {
	    line = line.trim();
	    if (line.indexOf('#') == 0) {
		line = "";
	    }
	}
	return line;
    }

    /**
     * Insert a preference expression and value into a given map.
     */
    private void mapPut(Map map, String name, Object preference)
	throws IOException
    {
	isEmpty = false;
	
	if ((name == null) || (name.length() == 0)) {
	    throw new IOException("no name specified in preference" +
				  " expression");
	}
	if (map.put(name, preference) != null) {
	    throw new IOException("duplicate map entry: " + name);
	}
    }

    /**
     * Write the preferences to the specified OutputStream using the
     * preference list syntax. Preference expressions are written in
     * the following order:
     *
     * Complete name expressions
     *
     * Package expressions
     * 
     * Namespace expressions
     *
     * @param out the stream to which formatted preference information
     * will be written
     * @throws IOException if an error occurs while writing to the stream
     */
    public void write(OutputStream out) throws IOException {
        BufferedWriter bw = new BufferedWriter
            (new OutputStreamWriter(out, "UTF8"));

	/* write the specification header */
        bw.write(HEADER_TITLE + "1.0\n");
	bw.write(PREF_PREFIX + " " + defaultPreference + "\n\n");

	/* write out most specific preferences first */
        synchronized (completeNamePrefs){
            writeMap(completeNamePrefs, bw, "");
        }
        synchronized (packagePrefs){
            writeMap(packagePrefs, bw, "/*");
        }
        synchronized (namespacePrefs){
            writeMap(namespacePrefs, bw, "/-");
        }

	bw.flush();
    }

    /**
     * Write the contents of the map into out using the
     * preference syntax.
     */
    private void writeMap(Map prefs, Writer out, String suffix)
	throws IOException
    {
	Iterator i = (new TreeSet(prefs.keySet())).iterator();
	while (i.hasNext()) {
	    String current = i.next();
	    out.write(NAME_PREFIX + " " + current +
		      suffix + "\n");
	    Object value = prefs.get(current);
	    if (value instanceof Boolean) {
		out.write(PREF_PREFIX + " " + value + "\n\n");
	    } else if (value instanceof Integer) {
		int state = ((Integer) value).intValue();
		if ((state == NAME_PREFERRED) ||
		    (state == NAME_PREFERRED_RESOURCE_EXISTS))
		{
		    out.write(PREF_PREFIX + " " + true + "\n\n");
		} else {
		    out.write(PREF_PREFIX + " " + false + "\n\n");   
		}
	    }
	}
    }

    /**
     * Returns the preference setting that will be applied to names
     * which have no explicit preference setting in contained
     * preference settings.  The default preference is set by the
     * first preference setting with no associated name in a
     * preferences list file.
     *
     * @return default boolean preference value for these preferences
     */
    public Boolean getDefaultPreference() {
	return defaultPreference;
    }
    
    /**
     * Enable MarshalInputStream to optimize preference information:
     * permits complete name expressions to be added for names that
     * only match wild-card expressions.  These added expressions hold
     * data that tells if a resource has been loaded.
     *
     * This method makes this object mutable.  Synchronization must be
     * used to ensure consistent preference state when this method is
     * called.
     *
     * @param name the name for which preferred state will be set
     * @param prefState the preferred state for the given name
     * @throws IOException if the name length is zero length
     */
    public void setNameState(String name, int prefState)
	throws IOException
    {
	isEmpty = false;
	
	if (name.length() == 0) {
	    throw new IOException("no name specified in preference" +
				  " expression");
	}
        synchronized (completeNamePrefs){
            completeNamePrefs.put(name, Integer.valueOf(prefState));
        }
    }

    /**
     * Searches the preference maps to determine the preference state
     * of the named resource.  The preference state for the given
     * resource name is returned.  The preference state is an integer
     * that is equal to one of the preference state values defined
     * above.  This state integer tells the preference value of name
     * and whether its resource is known to exist.
     *
     * @param name
     * @param isClass whether the given name refers to a
     *        class resource
     *
     * @return the state for the given name which will be set to one
     *         of the following values: NAME_NO_PREFERENCE,
     *         NAME_NOT_PREFERRED, NAME_PREFERRED,
     *         NAME_PREFERRED_RESOURCE_EXISTS
     * 
     * @throws IOException if an error occurs getting the state for
     *         the supplied name
     */
    public int getNameState(String name, boolean isClass)
	throws IOException
    {
	Integer state;

	if (isClass) {
	    state = getClassNameState(name);
	} else {
            synchronized (completeNamePrefs){
                state = (Integer) completeNamePrefs.get(name);
            }
	}
	if (state != null) {
	    return state.intValue();
	}
	return NAME_NO_PREFERENCE;
    }

    /**
     * Returns the preference state for a given name (as is done in
     * getNonclassNameState) but also interprets the notation for
     * inner classes as a wild card so that the preference value for a
     * container class propagates to the classes it contains.  More
     * specific inner classes names override more general container
     * preference.
     */
    private Integer getClassNameState(String name) throws IOException {
	if (!name.endsWith(".class")) {
	    throw new IOException("requested name state on a " +
				  "non-class resource: " + name);
	}
	
	Integer state = null;	
	String container = name;
	int lastDollar = -1;
        synchronized(completeNamePrefs){
            do {
                state = (Integer) completeNamePrefs.get(container);
                if (state == null) {
                    lastDollar = container.lastIndexOf("$");
                    if (lastDollar >= 0) {
                        container =
                            container.substring(0, lastDollar) + ".class";
                    }
                }
            } while ((lastDollar >= 0) && (state == null));
        }
	return state;
    }
    
    /**
     * Return the boolean value of the most specific wild card
     * (package and namespace) expression which matches
     * name.  Package preferences are always more
     * specific than namespace preferences.
     *
     * @param name the resource name to which the returned boolean
     * value will apply
     *
     * @return Boolean.TRUE if name is
     * preferred. Boolean.FALSE is it is
     * not. null if there is no wildcard preference for
     * the name.
     */
    public Boolean getWildcardPreference(String name) {
	Boolean wildcardPref = null;
	
	int lastSlash = name.lastIndexOf("/");
	if (lastSlash >= 0) {
	    String mostSpecific = name.substring(0, lastSlash);
	    if (!mostSpecific.equals("")) {
                synchronized (packagePrefs){
                    if (!packagePrefs.isEmpty()) {
                        wildcardPref = (Boolean)
                            packagePrefs.get(mostSpecific);
                    }
                }
		if (wildcardPref == null) {
		    wildcardPref = getNamespacePreference(mostSpecific);
		}
	    }
	}

	return wildcardPref;
    }

    /**
     * Return a Boolean for the most specific namespace expression
     * which matches name. null if the name does not
     * match a namespace preference expression.
     */
    private Boolean getNamespacePreference(String namespace) {
	Boolean namespacePref = null;
	synchronized (namespacePrefs){
            if (!namespacePrefs.isEmpty()) {
                int lastSlash;
                do {
                    namespacePref =
                        (Boolean) namespacePrefs.get(namespace);

                    lastSlash = namespace.lastIndexOf("/");
                    if (lastSlash >= 0) {
                        namespace = namespace.substring(0, lastSlash);
                    }

                } while ((lastSlash >= 0) &&
                         (!namespace.equals("")) &&
                         namespacePref == null);
            }
        }
	return namespacePref;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy