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

de.schlichtherle.io.archive.spi.AbstractArchiveDriver Maven / Gradle / Ivy

/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed 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 de.schlichtherle.io.archive.spi;

import de.schlichtherle.io.archive.Archive;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;

/**
 * An abstract archive driver implementation to ease the task of developing
 * an archive driver.
 * It provides default implementations for character sets and icon handling.
 * 

* Since TrueZIP 6.4, this class is serializable in order to meet the * requirements of the {@link de.schlichtherle.io.File} class. * * @author Christian Schlichtherle * @version $Id: AbstractArchiveDriver.java 06f3ba684701 2010/11/04 01:15:55 christian $ * @since TrueZIP 6.0 */ public abstract class AbstractArchiveDriver implements ArchiveDriver, Serializable { private static final long serialVersionUID = 6546816846546846516L; private static final String CLASS_NAME = "de.schlichtherle.io.archive.spi.AbstractArchiveDriver"; private static final Logger logger = Logger.getLogger(CLASS_NAME, CLASS_NAME); private final String charset; private final Icon openIcon, closedIcon; /** * This field should be considered to be {@code final}! * * @see #ensureEncodable */ private transient ThreadLocalEncoder encoder; // never transmit this over the wire! /** * Constructs a new abstract archive driver. * * @param charset The name of a character set to use by default for all * entry names and probably other meta data when reading or writing * archive files. * @param openIcon The icon to return by {@link #getOpenIcon}. * May be {@code null}. * @param closedIcon The icon to return by {@link #getClosedIcon}. * May be {@code null}. * @throws NullPointerException If {@code charset} is * {@code null}. * @throws UnsupportedCharsetException If {@code charset} is not * supported by both the JSE 1.1 API and JSE 1.4 API. * @throws InconsistentCharsetSupportError If {@code charset} is * supported by the JSE 1.1 API, but not the JSE 1.4 API, * or vice versa. */ protected AbstractArchiveDriver( final String charset, final Icon openIcon, final Icon closedIcon) { this.charset = charset; this.encoder = new ThreadLocalEncoder(); this.openIcon = openIcon; this.closedIcon = closedIcon; // Perform fail fast tests for character set charsets using both // JSE 1.1 API and the NIO API. final UnsupportedEncodingException uee = testJSE11Support(charset); final UnsupportedCharsetException uce = testJSE14Support(charset); if (uee != null || uce != null) { if (uee == null) throw new InconsistentCharsetSupportError(charset, uce); if (uce == null) throw new InconsistentCharsetSupportError(charset, uee); throw uce; // throw away uee - it has same reason } assert invariants(); } private static UnsupportedEncodingException testJSE11Support( final String charset) { try { new String(new byte[0], charset); } catch (UnsupportedEncodingException ex) { return ex; } return null; } private static UnsupportedCharsetException testJSE14Support( final String charset) { try { final Charset impl = Charset.forName(charset); logger.log(Level.CONFIG, "charset.class", new Object[] { // NOI18N charset, impl.name(), impl.getClass().getName(), }); } catch (UnsupportedCharsetException ex) { return ex; } return null; } /** * Postfixes the instance after its default deserialization. * * @throws InvalidObjectException If the instance invariants are not met. */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); assert encoder == null; encoder = new ThreadLocalEncoder(); try { invariants(); } catch (AssertionError ex) { throw (InvalidObjectException) new InvalidObjectException(ex.toString()).initCause(ex); } } /** * Checks the invariants of this class and throws an AssertionError if * any is violated even if assertion checking is disabled. *

* The constructors call this method like this: *

{@code assert invariants(); }
* This calls the method if and only if assertions are enabled in order * to assert that the instance invariants are properly obeyed. * If assertions are disabled, the call to this method is thrown away by * the HotSpot compiler, so there is no performance penalty. *

* When deserializing however, this method is called regardless of the * assertion status. On error, the {@link AssertionError} is wrapped * in an {@link InvalidObjectException} and thrown instead. * * @throws AssertionError If any invariant is violated even if assertions * are disabled. * @return {@code true} */ private boolean invariants() { if (charset == null) throw new AssertionError("character set not initialized"); try { ensureEncodable(""); } catch (CharConversionException ex) { throw new AssertionError(ex); } return true; } /** * Ensures that the given entry name is representable in this driver's * character set charset. * Should be called by sub classes in their implementation of the method * {@link ArchiveDriver#createArchiveEntry}. * * @param entryName A valid archive entry name - {@code null} is not * permissible. * @see #getCharset * @see Requirements for Archive Entry Names * @throws CharConversionException If the entry name contains characters * which cannot get encoded. */ protected final void ensureEncodable(String entryName) throws CharConversionException { if (!encoder.canEncode(entryName)) throw new CharConversionException(entryName + " (illegal characters in entry name)"); } /** * Returns the value of the property {@code charset} which was * provided to the constructor. */ public final String getCharset() { return charset; } /** @deprecated Use {@link #getCharset} instead. */ public final String getEncoding() { return charset; } /** * Returns the value of the property {@code openIcon} which was * provided to the constructor. * * @param archive Ignored. */ public final Icon getOpenIcon(Archive archive) { return openIcon; } /** * Returns the value of the property {@code closedIcon} which was * provided to the constructor. * * @param archive Ignored. */ public final Icon getClosedIcon(Archive archive) { return closedIcon; } private final class ThreadLocalEncoder extends ThreadLocal { protected Object initialValue() { return Charset.forName(charset).newEncoder(); } boolean canEncode(CharSequence cs) { return ((CharsetEncoder) get()).canEncode(cs); } } /** * Thrown to indicate that the character set implementation in the Java * Runtime Environment (JRE) for the Java Standard Edition (JSE) is broken * and needs fixing. *

* This error is thrown if and only if the character set provided to the * constructor of the enclosing class is either supported by the JSE 1.1 * style API ({@link String#String(byte[], String)}), but not the JSE 1.4 * style API ({@link Charset#forName(String)}), or vice versa. * This implies that this error is not thrown if the character * set is consistently supported or not supported by both APIs! *

* Most of the time, this error happens when accessing regular ZIP files. * The respective archive drivers require "IBM437" as the * character set. * Unfortunately, this character set is optional and Sun's JSE * implementations usually only install it if the JSE has been fully * installed. Its provider is then located in * $JAVA_HOME/lib/charsets.jar, where $JAVA_HOME is the * path name of the installed JRE. *

* To ensure that "IBM437" is always available regardless of * the JRE installation, TrueZIP provides its own provider for this charset. * This provider is configured in * truezip.jar/META-INF/services/java.nio.charset.spi.CharsetProvider. * So you should actually never see this happening (cruel world - sigh...). *

* Because the detected inconsistency would cause subtle bugs in archive * drivers and may affect other applications, too, it needs fixing. * Your options in order of preference: *

    *
  1. Upgrade to a more recent JRE or reinstall it. * When asked during installation, make sure to do a "full install". *
  2. Fix the JRE by copying $JAVA_HOME/lib/charsets.jar from some * other distribution. *
* This should ensure that $JAVA_HOME/lib/charsets.jar is present in the * JRE, which contains the provider for the "IBM437" character * set. * Although this should not be necessary due to TrueZIP's own provider, * this seems to fix the issue. *

* This error class has protected visibility solely for the purpose of * documenting it in the Javadoc. */ protected static final class InconsistentCharsetSupportError extends Error { private static final long serialVersionUID = 5976345821010992606L; private InconsistentCharsetSupportError(String charset, Exception cause) { super(message(charset, cause), cause); } private static String message( final String charset, final Exception cause) { assert cause instanceof UnsupportedEncodingException || cause instanceof UnsupportedCharsetException; final String[] api = cause instanceof UnsupportedEncodingException ? new String[] { "J2SE 1.4", "JSE 1.1" } : new String[] { "JSE 1.1", "J2SE 1.4" }; return "The character set '" + charset + "' is supported by the " + api[0] + " API, but not the " + api[1] + " API." + "\nThis requires fixing the Java Runtime Environment!" + "\nPlease read the Javadoc of this error class for more information."; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy