CharsetDetector provides a facility for detecting the
* charset or encoding of character data in an unknown format.
* The input data can either be from an input stream or an array of bytes.
* The result of the detection operation is a list of possibly matching
* charsets, or, for simple use, you can just ask for a Java Reader that
* will will work over the input data.
* * Character set detection is at best an imprecise operation. The detection * process will attempt to identify the charset that best matches the characteristics * of the byte data, but the process is partly statistical in nature, and * the results can not be guaranteed to always be correct. *
* For best accuracy in charset detection, the input data should be primarily * in a single language, and a minimum of a few hundred bytes worth of plain text * in the language are needed. The detection process will attempt to * ignore html or xml style markup that could otherwise obscure the content. * stable ICU 3.4 */ public class CharsetDetector { // Question: Should we have getters corresponding to the setters for input text // and declared encoding? // A thought: If we were to create our own type of Java Reader, we could defer // figuring out an actual charset for data that starts out with too much English // only ASCII until the user actually read through to something that didn't look // like 7 bit English. If nothing else ever appeared, we would never need to // actually choose the "real" charset. All assuming that the application just // wants the data, and doesn't care about a char set name. /** * Constructor * */ public CharsetDetector() { } /** * Set the declared encoding for charset detection. * The declared encoding of an input text is an encoding obtained * from an http header or xml declaration or similar source that * can be provided as additional information to the charset detector. * A match between a declared encoding and a possible detected encoding * will raise the quality of that detected encoding by a small delta, * and will also appear as a "reason" for the match. *
* A declared encoding that is incompatible with the input data being * analyzed will not be added to the list of possible encodings. * * @param encoding The declared encoding * @return this * */ public CharsetDetector setDeclaredEncoding(String encoding) { fDeclaredEncoding = encoding; return this; } /** * Set the input text (byte) data whose charset is to be detected. * * @param in the input text of unknown encoding * * @return This CharsetDetector * */ public CharsetDetector setText(byte [] in) { fRawInput = in; fRawLength = in.length; return this; } private static final int kBufSize = 8000; /** * Set the input text (byte) data whose charset is to be detected. *
* The input stream that supplies the character data must have markSupported() * == true; the charset detection process will read a small amount of data, * then return the stream to its original position via * the InputStream.reset() operation. The exact amount that will * be read depends on the characteristics of the data itself. * * @param in the input text of unknown encoding * * @return This CharsetDetector * * @throws IOException if an I/O error occurs. * */ public CharsetDetector setText(InputStream in) throws IOException { fInputStream = in; fInputStream.mark(kBufSize); fRawInput = new byte[kBufSize]; // Always make a new buffer because the // previous one may have come from the caller, // in which case we can't touch it. fRawLength = 0; int remainingLength = kBufSize; while (remainingLength > 0 ) { // read() may give data in smallish chunks, esp. for remote sources. Hence, this loop. int bytesRead = fInputStream.read(fRawInput, fRawLength, remainingLength); if (bytesRead <= 0) { break; } fRawLength += bytesRead; remainingLength -= bytesRead; } fInputStream.reset(); return this; } /** * Return the charset that best matches the supplied input data. * Note though, that because the detection * only looks at the start of the input data, * there is a possibility that the returned charset will fail to handle * the full set of input data. *
* Raise an exception if *
null if there are no matches.
*
*/
public CharsetMatch detect() {
// TODO: A better implementation would be to copy the detect loop from
// detectAll(), and cut it short as soon as a match with a high confidence
// is found. This is something to be done later, after things are otherwise
// working.
CharsetMatch[] matches = detectAll();
if (matches == null || matches.length == 0) {
return null;
}
return matches[0];
}
/**
* Return an array of all charsets that appear to be plausible
* matches with the input data. The array is ordered with the
* best quality match first.
* * Raise an exception if *
* This is a convenience method that is equivalent to
* this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getReader();
*
* For the input stream that supplies the character data, markSupported() * must be true; the charset detection will read a small amount of data, * then return the stream to its original position via * the InputStream.reset() operation. The exact amount that will * be read depends on the characteristics of the data itself. *
* Raise an exception if no charsets appear to match the input data. * * @param in The source of the byte data in the unknown charset. * * @param declaredEncoding A declared encoding for the data, if available, * or null or an empty string if none is available. * * @return Reader to access the converted input data * */ public Reader getReader(InputStream in, String declaredEncoding) { fDeclaredEncoding = declaredEncoding; try { setText(in); CharsetMatch match = detect(); if (match == null) { return null; } return match.getReader(); } catch (IOException e) { return null; } } /** * Autodetect the charset of an inputStream, and return a String * containing the converted input data. *
* This is a convenience method that is equivalent to
* this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getString();
*
* Raise an exception if no charsets appear to match the input data.
*
* @param in The source of the byte data in the unknown charset.
*
* @param declaredEncoding A declared encoding for the data, if available,
* or null or an empty string if none is available.
*
* @return a String containing the converted input data
*
*/
public String getString(byte[] in, String declaredEncoding) {
fDeclaredEncoding = declaredEncoding;
try {
setText(in);
CharsetMatch match = detect();
if (match == null) {
return null;
}
return match.getString(-1);
} catch (IOException e) {
return null;
}
}
/**
* Get the names of all charsets supported by CharsetDetector class.
*
* Note: Multiple different charset encodings in a same family may use
* a single shared name in this implementation. For example, this method returns
* an array including "ISO-8859-1" (ISO Latin 1), but not including "windows-1252"
* (Windows Latin 1). However, actual detection result could be "windows-1252"
* when the input data matches Latin 1 code points with any points only available
* in "windows-1252".
*
* @return an array of the names of all charsets supported by
*
* Instances of this class are created only by CharsetDetectors.
*
* Note: this class has a natural ordering that is inconsistent with equals.
* The natural ordering is based on the match confidence value.
*
* stable ICU 3.4
*/
public class CharsetMatch implements Comparable
* CAUTION: if the source of the byte data was an InputStream, a Reader
* can be created for only one matching char set using this method. If more
* than one charset needs to be tried, the caller will need to reset
* the InputStream and create InputStreamReaders itself, based on the charset name.
*
* @return the Reader for the Unicode character data.
*
*/
public Reader getReader() {
InputStream inputStream = fInputStream;
if (inputStream == null) {
inputStream = new ByteArrayInputStream(fRawInput, 0, fRawLength);
}
try {
inputStream.reset();
return new InputStreamReader(inputStream, getName());
} catch (IOException e) {
return null;
}
}
/**
* reate a Java String from Unicode character data corresponding
* to the original byte data supplied to the Charset detect operation.
*
* @return a String created from the converted input data.
*
* @throws IOException if an IO error occurs.
*
*/
public String getString() throws IOException {
return getString(-1);
}
/**
* create a Java String from Unicode character data corresponding
* to the original byte data supplied to the Charset detect operation.
* The length of the returned string is limited to the specified size;
* the string will be trunctated to this length if necessary. A limit value of
* zero or less is ignored, and treated as no limit.
*
* @param maxLength The maximium length of the String to be created when the
* source of the data is an input stream, or -1 for
* unlimited length.
* @return a String created from the converted input data.
*
* @throws IOException if an IO error occurs.
*
*/
String getString(int maxLength) throws IOException {
String result;
if (fInputStream != null) {
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
Reader reader = getReader();
int max = maxLength < 0? Integer.MAX_VALUE : maxLength;
int bytesRead;
while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {
sb.append(buffer, 0, bytesRead);
max -= bytesRead;
}
reader.close();
return sb.toString();
} else {
String name = getName();
/*
* getName() may return a name with a suffix 'rtl' or 'ltr'. This cannot
* be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'
* should be stripped off before creating the string.
*/
int startSuffix = !name.contains("_rtl") ? name.indexOf("_ltr") : name.indexOf("_rtl");
if (startSuffix > 0) {
name = name.substring(0, startSuffix);
}
result = new String(fRawInput, name);
}
return result;
}
/**
* Get an indication of the confidence in the charset detected.
* Confidence values range from 0-100, with larger numbers indicating
* a better match of the input data to the characteristics of the charset.
* @return the confidence in the charset match
*/
public int getConfidence() {
return fConfidence;
}
/**
* Get the name of the detected charset.
* The name will be one that can be used with other APIs on the
* platform that accept charset names. It is the "Canonical name"
* as defined by the class java.nio.charset.Charset; for
* charsets that are registered with the IANA charset registry,
* this is the MIME-preferred registered name.
*
* @see java.nio.charset.Charset
* @see java.io.InputStreamReader
*
* @return The name of the charset.
*
*/
public String getName() {
return fCharsetName;
}
/**
* Get the ISO code for the language of the detected charset.
*
* @return The ISO code for the language or
* This folder is:
*
* If the default preferences folder doesn't exist, this method will create it.
*
* @return the path to the default trolCommander preferences folder.
*/
public static AbstractFile getDefaultPreferencesFolder() {
return FileFactory.getFile(getDefaultPreferencesFolderPath());
}
/**
* Returns the path to the default muCommander preferences folder.
*
* This folder is:
*
* If the default preferences folder doesn't exist, this method will create it.
*
* @return the path to the default trolCommander preferences folder.
*/
public static String getDefaultPreferencesFolderPath() {
File folder;
// Mac OS X specific folder (~/Library/Preferences/trolCommander)
if (OsFamily.MAC_OS_X.isCurrent()) {
folder = new File(System.getProperty("user.home") + "/Library/Preferences/trolCommander");
// For all other platforms, use generic folder (~/.trolcommander)
} else {
folder = new File(System.getProperty("user.home"), "/.trolcommander");
}
// Makes sure the folder exists.
if (!folder.exists()) {
if (!folder.mkdir()) {
LOGGER.warn("Could not create preference folder: {}", folder.getAbsolutePath());
}
}
return folder.getAbsolutePath();
}
/**
* Returns the path to the folder that contains all the user's data.
*
* All modules that save user data to a file should do so in a file located in
* the folder returned by this method.
*
* The value returned by this method can be set through {@link #setPreferencesFolder(File)}.
* Otherwise, the {@link #getDefaultPreferencesFolder() default preference folder} will be
* used.
*
* @return the path to the user's preference folder.
* @see #setPreferencesFolder(AbstractFile)
*/
public static AbstractFile getPreferencesFolder() {
// If the preferences folder has been set, use it.
if (prefFolder != null) {
return prefFolder;
}
return getDefaultPreferencesFolder();
}
/**
* Sets the path to the folder in which trolCommander will look for its preferences.
*
* If
* If
* If
* This class is used to start muCommander. It will analyse command line
* arguments, initialize the whole software and start the main window.
*
* @author Maxence Bernard, Nicolas Rinaudo, Oleg Trifonov
*/
public class TrolCommander {
private static Logger logger;
/** true while the application is launching, false after it has finished launching */
private static boolean isLaunching = true;
/** Launch lock. */
private static final Object LAUNCH_LOCK = new Object();
/**
* Prevents initialization of the Note: the items list is refreshed each time the menu is selected. In other words, a new instance of AdbMenu
* does not have to be created in order to see new devices.
*
* Created on 28/12/15.
* @author Oleg Trifonov
*/
public abstract class AndroidMenu extends JMenu implements MenuListener {
/**
* Creates a new instance of
* Two types of {@link CredentialsMapping} are used:
* The returned credentials will match the given URL's scheme and host, but the path may differ so there is
* no guarantee that the credentials will successfully authenticate the location.
*
* The best match (credentials with the 'closest' path to the provided location's path) is returned at the first
* position ([0]), if there is at least one matching credentials instance. The returned array can be empty
* (zero length) but never null.
*
* @param location the location to be compared against known credentials instances, both volatile and persistent
* @return an array of CredentialsMapping matching the given URL's scheme and host, best match at the first position
*/
public static CredentialsMapping[] getMatchingCredentials(FileURL location) {
// Retrieve matches
List Depending on value returned by {@link CredentialsMapping#isPersistent()}, the credentials will either be stored
* in the volatile credentials list or the persistent one. Any existing credentials mapped to the same realm
* will be replaced by the provided ones.
*
* This method should be called when new credentials have been entered by the user, after they have been validated
* by the application (i.e. access was granted to the location).
*
* @param credentialsMapping credentials to be added to the list of known credentials
*/
public static void addCredentials(CredentialsMapping credentialsMapping) {
// Do not add if the credentials are empty
if (credentialsMapping.getCredentials().isEmpty()) {
return;
}
boolean persist = credentialsMapping.isPersistent();
getLogger().trace("called, realm="+ credentialsMapping.getRealm()+" isPersistent="+ credentialsMapping.isPersistent());
getLogger().trace("before, persistentCredentials="+ persistentCredentialMappings);
getLogger().trace("before, volatileCredentials="+ volatileCredentialMappings);
if (persist) {
replaceListElement(persistentCredentialMappings, credentialsMapping);
volatileCredentialMappings.remove(credentialsMapping);
} else {
replaceListElement(volatileCredentialMappings, credentialsMapping);
persistentCredentialMappings.removeElement(credentialsMapping);
}
getLogger().trace("after, persistentCredentials="+ persistentCredentialMappings);
getLogger().trace("after, volatileCredentials="+ volatileCredentialMappings);
}
/**
* Use the credentials and realm properties of the specified Any credentials contained by the Credentials are first looked for using {@link #getMatchingCredentials(com.mucommander.commons.file.FileURL)}.
* If there is no match, guest credentials are retrieved from the URL and used (if any).
*
* @param location the FileURL to authenticate
*/
private static void authenticateImplicit(FileURL location) {
getLogger().trace("called, fileURL="+ location +" containsCredentials="+ location.containsCredentials());
CredentialsMapping[] creds = getMatchingCredentials(location);
if (creds.length > 0) {
authenticate(location, creds[0]);
} else {
Credentials guestCredentials = location.getGuestCredentials();
if(guestCredentials!=null) {
authenticate(location, new CredentialsMapping(guestCredentials, location.getRealm(), false));
}
}
}
/**
* Looks for credentials matching the specified location in the given credentials Vector and adds them to the given
* matches Vector.
*
* @param location the location to find matching credentials for
* @param credentials the Vector containing the CredentialsMapping instances to compare to the given location
* @param matches the Vector where matching CredentialsMapping instances will be added
*/
private static void findMatches(FileURL location, List
* The path of each matching CredentialsMapping' location is compared to the provided location's path: the more
* folder parts match, the better. If both paths are equal, then the CredentialsMapping index is returned (perfect match).
*
* @param location the location to be compared against CredentialsMapping matches
* @param matches CredentialsMapping instances matching the given location
* @return the CredentialsMapping instance that best matches the given location, -1 if the given matches Vector is empty.
*/
private static int getBestMatchIndex(FileURL location, List
* The returned Vector instance is the one actually used by CredentialsManager, so use it with caution.
*
* @return the list of known volatile {@link CredentialsMapping}.
*/
public static List
* Any changes made to the Vector will be detected and will yield to writing the credentials file when
* {@link #writeCredentials(boolean)} is called with false.
*
* @return the list of known persistent {@link CredentialsMapping}.
*/
public static AlteredVector
* Note: the returned {@link FileURL} does not contain any credentials.
*
* @return the location associated with the credentials.
*/
public FileURL getRealm() {
return realm;
}
/**
* Returns
* Note: the version attribute was introduced in muCommander 0.8.4.
*
* @return the muCommander version that was used to write the credentials file, Use {@link #getServices()} to get a list of currently available Bonjour services.
*
* @author Maxence Bernard
* @see BonjourMenu
*/
public class BonjourDirectory implements ServiceListener {
private static Logger logger;
/** Singleton instance held to prevent garbage collection and also used for synchronization */
private final static BonjourDirectory instance = new BonjourDirectory();
/** Does all the hard work */
private static JmDNS jmDNS;
/** List of discovered and currently active Bonjour services */
private static final List Note: the items list is refreshed each time the menu is selected. In other words, a new instance of BonjourMenu
* does not have to be created in order to see new Bonjour services.
*
* @author Maxence Bernard
*/
public abstract class BonjourMenu extends JMenu implements MenuListener {
/**
* Creates a new instance of Bookmarks are simple name/location pairs:
*
* It monitors any changes made to the bookmarks and when changes are made, fires change events to registered
* listeners.
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class BookmarkManager implements VectorChangeListener {
/** Whether we're currently loading the bookmarks or not. */
private static boolean isLoading = false;
/** Bookmarks file location */
private static AbstractFile bookmarksFile;
/** Default bookmarks file name */
private static final String DEFAULT_BOOKMARKS_FILE_NAME = "bookmarks.xml";
/** Bookmark instances */
private static final AlteredVector
* If it hasn't been changed through a call to {@link #setBookmarksFile(String)},
* this method will return the default, system dependant bookmarks file.
*
* @return the path to the bookmark file.
* @see #setBookmarksFile(String)
* @throws IOException if there was a problem locating the default bookmarks file.
*/
private static synchronized AbstractFile getBookmarksFile() throws IOException {
if (bookmarksFile == null) {
return PlatformManager.getPreferencesFolder().getChild(DEFAULT_BOOKMARKS_FILE_NAME);
}
return bookmarksFile;
}
/**
* Sets the path to the bookmarks file.
*
* This is a convenience method and is strictly equivalent to calling
* This is a convenience method and is strictly equivalent to calling Important: the returned Vector should not directly be used to
* add or remove bookmarks, doing so won't trigger any event to registered bookmark listeners.
* However, it is safe to modify bookmarks individually, events will be properly fired.
* @return an {@link AlteredVector} that contains all bookmarks.
*/
public static synchronized AlteredVector Listeners are stored as weak references so {@link #removeBookmarkListener(BookmarkListener)}
* doesn't need to be called for listeners to be garbage collected when they're not used anymore.
*
* @param listener the BookmarkListener to add to the list of registered listeners.
* @see #removeBookmarkListener(BookmarkListener)
*/
public static void addBookmarkListener(BookmarkListener listener) {
synchronized(listeners) {
listeners.put(listener, null);
}
}
/**
* Removes the specified BookmarkListener from the list of registered listeners.
*
* @param listener the BookmarkListener to remove from the list of registered listeners.
* @see #addBookmarkListener(BookmarkListener)
*/
private static void removeBookmarkListener(BookmarkListener listener) {
synchronized(listeners) {
listeners.remove(listener);
}
}
/**
* Notifies all the registered bookmark listeners of a bookmark change. This can be :
* If true is specified, any subsequent calls to fireBookmarksChanged will be ignored, until this method is
* called again with false.
* @param b whether to fire events.
*/
public static synchronized void setFireEvents(boolean b) {
if (b) {
// Fire a bookmarks changed event if bookmarks were modified during event pause
if (!fireEvents && lastBookmarkChangeTime >= lastEventPauseTime) {
fireEvents = true;
fireBookmarksChanged();
}
} else {
// Remember pause start time
if (fireEvents) {
fireEvents = false;
lastEventPauseTime = System.currentTimeMillis();
}
}
}
/////////////////////////////////////////
// VectorChangeListener implementation //
/////////////////////////////////////////
public void elementsAdded(int startIndex, int nbAdded) {
fireBookmarksChanged();
}
public void elementsRemoved(int startIndex, int nbRemoved) {
fireBookmarksChanged();
}
public void elementChanged(int index) {
fireBookmarksChanged();
}
// - Bookmark loading ------------------------------------------------------
// -------------------------------------------------------------------------
private static class Loader implements BookmarkBuilder {
public void startBookmarks() {
}
public void endBookmarks() {
}
public void addBookmark(String name, String location, String parent) {
BookmarkManager.addBookmark(new Bookmark(name, location, parent));
}
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/BookmarkParser.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* Note: the version attribute was introduced in muCommander 0.8.4.
*
* @return the muCommander version that was used to write the bookmarks file, Disclaimer: this obviously is weak encryption at most, the key used being static and public, and XOR
* encryption being easy to crack. This doesn't aim or pretend to be anything more than a way to scramble text
* without requiring a master password in the application.
*
* @author Maxence Bernard
*/
public class XORCipher {
/** Long enough key (256 bytes) to avoid having too much redundancy in small text strings. */
private final static int[] NOT_SO_PRIVATE_KEY = {
161, 220, 156, 76, 177, 174, 56, 37, 98, 93, 224, 19, 160, 95, 69, 140,
91, 138, 33, 114, 248, 57, 179, 17, 54, 172, 249, 58, 26, 181, 167, 231,
241, 185, 218, 174, 37, 102, 100, 26, 16, 214, 119, 29, 118, 151, 135, 175,
245, 247, 160, 188, 77, 173, 109, 255, 73, 44, 186, 211, 117, 236, 204, 58,
246, 210, 128, 33, 234, 218, 82, 188, 78, 229, 180, 108, 247, 200, 3, 142,
206, 45, 165, 111, 96, 72, 76, 81, 238, 186, 240, 167, 185, 152, 68, 228,
87, 142, 145, 7, 74, 12, 106, 94, 15, 218, 155, 71, 87, 136, 58, 40,
246, 94, 7, 89, 29, 0, 78, 204, 70, 220, 240, 127, 59, 184, 109, 106
};
/**
* Cyphers the given byte array using XOR symmetrical encryption with a static hard-coded key.
*
* @param b the byte array to encrypt/decrypt
* @return the encrypted/decrypted byte array
*/
private static byte[] xor(byte[] b) {
int len = b.length;
int keyLen = NOT_SO_PRIVATE_KEY.length;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = (byte) (b[i] ^ NOT_SO_PRIVATE_KEY[i % keyLen]);
}
return result;
}
/**
* Encrypts the given String using XOR cipher followed by Base64 encoding. The returned String will only contain
* alphanumeric characters.
*
* @param s the String to encrypt
* @return an XOR-Base64 encrypted String
*/
public static String encryptXORBase64(String s) {
// TODO:
// Important: String.getBytes() returns bytes in the platform's default encoding, which might vary across
// platforms. This may potentially cause problems when decrypting a string on a different platform from the one
// which served to encrypt it.
// It is however too late to change as it could prevent existing encrypted strings (credentials file) from being
// loaded after the application is updated.
return Base64Encoder.encode(xor(s.getBytes()));
}
/**
* Decrypts the given XOR-Base64 encrypted String and throws an IOException if the given String is not properly
* Base64-encoded.
*
* @param s a XOR-Base64 encrypted String
* @return the decrypted String
* @throws IOException if the given String is not properly Base64-encoded
*/
public static String decryptXORBase64(String s) throws IOException {
// TODO:
// Important: new String() creates a string using the platform's default encoding, which might vary across
// platforms. This may potentially cause problems when decrypting a string on a different platform from the one
// which served to encrypt it.
// It is however too late to change as it could prevent existing encrypted strings (credentials file) from being
// loaded after the application is updated.
return new String(xor(Base64Decoder.decodeAsBytes(s)));
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/file/BookmarkFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* Some methods need to have access to the underlying file. This, however, requires
* resolving the path which can be time consuming. Using this method ensures that the
* path is only resolved if necessary, and at most once.
*
* @return the
* A bookmark is said to exist if and only if it is known to the {@link com.mucommander.bookmark.BookmarkManager}.
*
* @return
// * Deleting a bookmark means unregistering it from the {@link com.mucommander.bookmark.BookmarkManager}.
// *
// */
// @Override
// public void delete() {
// BookmarkManager.removeBookmark(bookmark);
// }
@Override
@UnsupportedFileOperation
public void delete() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.DELETE);
}
// - Bookmark duplication --------------------------------------------------
// -------------------------------------------------------------------------
/**
* Tries to copy the bookmark to the specified destination.
*
* If the specified destination is an instance of
* Note that this method will remove any previous bookmark of the same name.
*
* @param name name of the new bookmark.
* @param location location of the new bookmark.
*/
public void addBookmark(String name, String location, String parent) {
// Creates the new bookmark and checks for conflicts.
Bookmark newBookmark = new Bookmark(name, location, parent);
// Old bookmark of the same name, if any.
Bookmark oldBookmark = BookmarkManager.getBookmark(name);
if (oldBookmark != null) {
BookmarkManager.removeBookmark(oldBookmark);
}
// Adds the new bookmark.
BookmarkManager.addBookmark(newBookmark);
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/file/BookmarkProtocolProvider.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see The only area this implementation is slow at, is checking for and removing expired elements which
* requires traversing all values and An LRU (Least Recently Used) cache can contain a fixed number of items (the capacity). When capacity is reached,
* the least recently used is removed. Each object retrieved with the {@link #get(Object) get()} method
* makes the requested item the most recently used one. Similarly, each object inserted using the
* {@link #add(Object, Object) add()} method makes the added item the most recently used one.
*
* This LRUCache provides an optional feature : the ability to assign a time-to-live for each or part of the
* items added. When the time-to-live of an item expires, the item is automatically removed from the cache and won't
* be returned by the {@link #get(Object) get()} method anymore.
*
* Implementation note: checking for expired items can be an expensive operation so it doesn't have
* to be done as soon as the item has expired, the expired items can live a bit longer in the cache if necessary.
* This method will return If the cache's capacity has been reached (cache is full):
*
* Will return the default, system dependant bookmarks file.
*
* @return the path to the bookmark file.
* @throws java.io.IOException if there was a problem locating the default history file.
*/
public static synchronized AbstractFile getHistoryFile(Type type) throws IOException {
return PlatformManager.getPreferencesFolder().getChild(type.fileName);
}
public void clear() {
history.clear();
instance = null;
}
}
================================================
FILE: src/main/java/com/mucommander/cache/WindowsStorage.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2016 Oleg Trifonov
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* Will return the default, system dependant bookmarks file.
*
* @return the path to the bookmark file.
* @throws java.io.IOException if there was a problem locating the default history file.
*/
private static synchronized AbstractFile getHistoryFile() throws IOException {
return PlatformManager.getPreferencesFolder().getChild(STORAGE_FILE_NAME);
}
public void clear() {
if (records != null) {
records.clear();
}
records = null;
instance = null;
}
}
================================================
FILE: src/main/java/com/mucommander/cache/package.html
================================================
* Association file parsing is done through the {@link #read(InputStream,AssociationBuilder) read} method, which is
* the only way to interact with this class.
*
* Note that while this class knows how to read the content of an association XML file, its role is not to interpret it. This
* is done by instances of {@link AssociationBuilder}.
*
* @see AssociationsXmlConstants
* @see AssociationBuilder
* @see AssociationWriter
* @author Nicolas Rinaudo
*/
public class AssociationReader extends DefaultHandler implements AssociationsXmlConstants {
/** Where to send building messages. */
private final AssociationBuilder builder;
private boolean isInAssociation;
/**
* Creates a new command reader.
* @param b where to send custom command events.
*/
private AssociationReader(AssociationBuilder b) {
builder = b;
}
/**
* Parses the content of the specified input stream.
*
* This method will go through the specified input stream and notify the builder of any new association declaration it
* encounters. Note that parsing is done in a very lenient fashion, and perfectly invalid XML files might not raise
* an exception. This is not a flaw in the parser, and both allows muCommander to be error resilient and the associations
* file format to be extended without having to rewrite most of this code.
*
* Note that even if an error occurs, both of the builder's {@link AssociationBuilder#startBuilding()} and
* {@link AssociationBuilder#endBuilding()} methods will still be called. Parsing will stop at the first error
* however, so while the builder is guaranteed to receive correct messages, it might not receive all declared
* associations.
*
* @param in where to read association data from.
* @param b where to send building events to.
* @throws IOException if any IO error occurs.
* @throws CommandException if any parse error occurs
* @see #read(InputStream,AssociationBuilder)
*/
public static void read(InputStream in, AssociationBuilder b) throws IOException, CommandException {
b.startBuilding();
try {
SAXParserFactory.newInstance().newSAXParser().parse(in, new AssociationReader(b));
} catch(ParserConfigurationException | SAXException e) {
throw new CommandException(e);
} finally {
b.endBuilding();
}
}
/**
* This method is public as an implementation side effect and should not be called directly.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
String buffer;
try {
if (!isInAssociation) {
if (qName.equals(ELEMENT_ASSOCIATION)) {
// Makes sure the required attributes are present.
if ((buffer = attributes.getValue(ATTRIBUTE_COMMAND)) == null) {
return;
}
isInAssociation = true;
builder.startAssociation(buffer);
}
} else {
switch (qName) {
case ELEMENT_MASK:
String caseSensitive;
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
if ((caseSensitive = attributes.getValue(ATTRIBUTE_CASE_SENSITIVE)) != null) {
builder.setMask(buffer, caseSensitive.equals(VALUE_TRUE));
} else {
builder.setMask(buffer, true);
}
break;
case ELEMENT_IS_HIDDEN:
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
builder.setIsHidden(buffer.equals(VALUE_TRUE));
break;
case ELEMENT_IS_SYMLINK:
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
builder.setIsSymlink(buffer.equals(VALUE_TRUE));
break;
case ELEMENT_IS_READABLE:
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
builder.setIsReadable(buffer.equals(VALUE_TRUE));
break;
case ELEMENT_IS_WRITABLE:
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
builder.setIsWritable(buffer.equals(VALUE_TRUE));
break;
case ELEMENT_IS_EXECUTABLE:
if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {
return;
}
builder.setIsExecutable(buffer.equals(VALUE_TRUE));
break;
}
}
} catch(CommandException e) {
throw new SAXException(e);
}
}
/**
* This method is public as an implementation side effect, but should not be called directly.
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals(ELEMENT_ASSOCIATION) && isInAssociation) {
try {
builder.endAssociation();
} catch(CommandException e) {
throw new SAXException(e);
}
isInAssociation = false;
}
}
}
================================================
FILE: src/main/java/com/mucommander/command/AssociationWriter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
* This interface is only meant as a convenient way of sharing the XML
* file format between the {@link AssociationWriter} and {@link AssociationReader}. It will be removed
* at bytecode optimisation time.
*
* Associations XML files must match the following DTD:
*
* A command is composed of three elements:
*
* The basic command syntax is fairly simple:
*
* It's also possible to include keywords in a command:
*
* Once a
* A command's executable tokens are typically meant to be used with {@link com.mucommander.process.ProcessRunner#execute(String[], AbstractFile)}
* in order to generate instances of {@link com.mucommander.process.AbstractProcess}.
*
* @author Nicolas Rinaudo
* @see CommandManager
* @see com.mucommander.process.ProcessRunner
* @see com.mucommander.process.AbstractProcess
*/
public class Command implements Comparable
* This is a convenience constructor and is strictly equivalent to calling
*
* This is a convenience constructor and is strictly equivalent to calling
*
* Returns true if command contains keywords referencing selected file, e.g. $f,$n,$p,$e,$b.
* Returns false otherwise, e.g. $j, $xyz, etc.
*
* @return whether this command contains keywords referencing selected file.
*/
public synchronized boolean hasSelectedFileKeyword() {
String[] tokens = getTokens();
for (String token : tokens) {
if (token.length() >= 2 && token.charAt(0) == KEYWORD_HEADER) {
char ch2 = token.charAt(1);
if (ch2 == KEYWORD_PATH || ch2 == KEYWORD_NAME || ch2 == KEYWORD_EXTENSION || ch2 == KEYWORD_NAME_WITHOUT_EXTENSION ||
ch2 == KEYWORD_PARENT) {
return true;
}
}
}
// No token with file referencing keyword found
return false;
}
/**
* Returns
* If it hasn't been set, returns this command's alias.
*
* @return the command's display name.
*/
public synchronized String getDisplayName() {
return displayName != null ? displayName : alias;
}
/**
* Returns
* Different classes, such as {@link CommandManager} or {@link CommandReader}, know how to
* generate a list of commands - from instances loaded in memory or from a file, for example.
* Implementing
* Instances of
* A (fairly useless) implementation might look like:
*
* This is a convenience method and is strictly equivalent to calling
*
* This is a convenience method and is stricly equivalent to calling
*
* This method guarantees that the builder's {@link CommandBuilder#startBuilding() startBuilding()} and
* {@link CommandBuilder#endBuilding() endBuilding()} methods will both be called even if an error occurs.
* If that happens however, it is entirely possible that not all commands will be passed to
* the builder.
*
* @param builder object that will receive commands list building messages.
* @param type if not null then build only commands with specified type
* @throws CommandException if anything goes wrong.
*/
public static void buildCommands(CommandBuilder builder, CommandType type) throws CommandException {
builder.startBuilding();
try {
// Goes through all the registered commands.
for (Command command : commands()) {
if (type == null || command.getType() == type) {
builder.addCommand(command);
}
}
} finally {
builder.endBuilding();
}
}
// - Associations building -------------------------------------------------
// -------------------------------------------------------------------------
private static void buildFilter(FileFilter filter, AssociationBuilder builder) throws CommandException {
// Filter on the file type.
if (filter instanceof AttributeFileFilter) {
AttributeFileFilter attributeFilter = (AttributeFileFilter)filter;
switch (attributeFilter.getAttribute()) {
case HIDDEN:
builder.setIsHidden(!attributeFilter.isInverted());
break;
case SYMLINK:
builder.setIsSymlink(!attributeFilter.isInverted());
break;
}
} else if (filter instanceof PermissionsFileFilter) {
PermissionsFileFilter permissionFilter = (PermissionsFileFilter)filter;
switch(permissionFilter.getPermission()) {
case PermissionTypes.READ_PERMISSION:
builder.setIsReadable(permissionFilter.getFilter());
break;
case PermissionTypes.WRITE_PERMISSION:
builder.setIsWritable(permissionFilter.getFilter());
break;
case PermissionTypes.EXECUTE_PERMISSION:
builder.setIsExecutable(permissionFilter.getFilter());
break;
}
} else if (filter instanceof RegexpFilenameFilter) {
RegexpFilenameFilter regexpFilter = (RegexpFilenameFilter)filter;
builder.setMask(regexpFilter.getRegularExpression(), regexpFilter.isCaseSensitive());
}
}
/**
* Passes all known file associations to the specified builder.
*
* This method guarantees that the builder's {@link AssociationBuilder#startBuilding() startBuilding()} and
* {@link AssociationBuilder#endBuilding() endBuilding()} methods will both be called even if an error occurs.
* If that happens however, it is entirely possible that not all associations will be passed to
* the builder.
*
* @param builder object that will receive association list building messages.
* @throws CommandException if anything goes wrong.
*/
public static void buildAssociations(AssociationBuilder builder) throws CommandException {
builder.startBuilding();
// Goes through all the registered associations.
try {
for (CommandAssociation current : associations) {
builder.startAssociation(current.getCommand().getAlias());
FileFilter filter = current.getFilter();
if (filter instanceof ChainedFileFilter) {
Iterator
* This method cannot guarantee the file's existence, and it's up to the caller
* to deal with the fact that the user might not actually have created custom
* associations.
*
* This method's return value can be modified through {@link #setAssociationFile(String)}.
* If this wasn't called, the default path will be used: {@link #DEFAULT_ASSOCIATION_FILE_NAME}
* in the {@link com.mucommander.PlatformManager#getPreferencesFolder() preferences} folder.
*
* @return the path to the custom associations XML file.
* @see #setAssociationFile(String)
* @see #loadAssociations()
* @see #writeAssociations()
* @throws IOException if there was an error locating the default commands file.
*/
public static AbstractFile getAssociationFile() throws IOException {
if (associationFile == null) {
return PlatformManager.getPreferencesFolder().getChild(DEFAULT_ASSOCIATION_FILE_NAME);
}
return associationFile;
}
/**
* Sets the path to the custom associations file.
*
* This is a convenience method and is strictly equivalent to calling
* This is a convenience method and is strictly equivalent to calling
* The command files will be loaded as a backed-up file (see {@link BackupInputStream}).
* Its format is described {@link AssociationsXmlConstants here}.
*
* @throws IOException if an IO error occurs.
* @throws CommandException thrown when errors occur while building custom commands
* @see #writeAssociations()
* @see #getAssociationFile()
* @see #setAssociationFile(String)
*/
public static void loadAssociations() throws IOException, CommandException {
AbstractFile file = getAssociationFile();
getLogger().debug("Loading associations from file: " + file.getAbsolutePath());
// Tries to load the associations file.
// Associations are not considered to be modified by this.
//InputStream in = null;
try (InputStream in = new BackupInputStream(file)) {
AssociationReader.read(in, new AssociationFactory());
} finally {
wereAssociationsModified = false;
}
}
/**,
* Writes all registered associations to the custom associations file.
*
* Data will be written to the path returned by {@link #getAssociationFile()}. Note, however,
* that this method will not actually do anything if the association list hasn't been modified
* since the last time it was saved.
*
* The association files will be saved as a backed-up file (see {@link BackupOutputStream}).
* Its format is described {@link AssociationsXmlConstants here}.
*
* @throws IOException if an I/O error occurs.
* @throws CommandException if an error occurs.
* @see #loadAssociations()
* @see #getAssociationFile()
* @see #setAssociationFile(String)
*/
public static void writeAssociations() throws CommandException, IOException {
// Do not save the associations if they were not modified.
if (!wereAssociationsModified) {
getLogger().debug("Custom file associations not modified, skip saving.");
return;
}
getLogger().debug("Writing associations to file: " + getAssociationFile());
// Writes the associations.
try (BackupOutputStream out = new BackupOutputStream(getAssociationFile())) {
buildAssociations(new AssociationWriter(out));
wereAssociationsModified = false;
}
}
// - Commands reading/writing ----------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the path to the custom commands XML file.
*
* This method cannot guarantee the file's existence, and it's up to the caller
* to deal with the fact that the user might not actually have created custom
* commands.
*
* This method's return value can be modified through {@link #setCommandFile(String)}.
* If this wasn't called, the default path will be used: {@link #DEFAULT_COMMANDS_FILE_NAME}
* in the {@link com.mucommander.PlatformManager#getPreferencesFolder() preferences} folder.
*
* @return the path to the custom commands XML file.
* @see #setCommandFile(String)
* @see #loadCommands()
* @see #writeCommands()
* @throws IOException if there was some error locating the default commands file.
*/
public static AbstractFile getCommandFile() throws IOException {
if (commandsFile == null) {
return PlatformManager.getPreferencesFolder().getChild(DEFAULT_COMMANDS_FILE_NAME);
}
return commandsFile;
}
/**
* Sets the path to the custom commands file.
*
* This is a convenience method and is strictly equivalent to calling
* This is a convenience method and is strictly equivalent to calling
* Data will be written to the path returned by {@link #getCommandFile()}. Note, however,
* that this method will not actually do anything if the command list hasn't been modified
* since the last time it was saved.
*
* The command files will be saved as a backed-up file (see {@link BackupOutputStream}).
* Its format is described {@link CommandsXmlConstants here}.
*
* @throws IOException if an I/O error occurs.
* @throws CommandException if an error occurs.
* @see #loadCommands()
* @see #getCommandFile()
* @see #setCommandFile(String)
*/
public static void writeCommands() throws IOException, CommandException {
// Only saves the command if they were modified since the last time they were written.
if (!wereCommandsModified) {
getLogger().debug("Custom commands not modified, skip saving.");
return;
}
getLogger().debug("Writing custom commands to file: " + getCommandFile());
// Writes the commands.
try (BackupOutputStream out = new BackupOutputStream(getCommandFile())) {
buildCommands(new CommandWriter(out), CommandType.NORMAL_COMMAND);
wereCommandsModified = false;
}
}
/**
* Loads the custom commands XML File.
*
* The command files will be loaded as a backed-up file (see {@link BackupInputStream}).
* Its format is described {@link CommandsXmlConstants here}.
*
* @throws IOException if an I/O error occurs.
* @see #writeCommands()
* @see #getCommandFile()
* @see #setCommandFile(String)
*/
public static void loadCommands() throws IOException, CommandException {
AbstractFile file = getCommandFile();
getLogger().debug("Loading custom commands from: " + file.getAbsolutePath());
// Tries to load the commands file.
// Commands are not considered to be modified by this.
try (InputStream in = new BackupInputStream(file)) {
CommandReader.read(in, new CommandManager());
} finally {
wereCommandsModified = false;
}
}
/*
private static void registerDefaultCommand(String alias, String command, String display) {
if(getCommandForAlias(alias) == null) {
if(command != null) {
// try {registerCommand(CommandParser.getCommand(alias, command, Command.SYSTEM_COMMAND, display));}
try {registerCommand(new Command(alias, command, Command.SYSTEM_COMMAND, display));}
catch(Exception e) {AppLogger.fine("Failed to register " + command + ": " + e.getMessage());}
}
}
}
*/
// - Unused methods --------------------------------------------------------
// -------------------------------------------------------------------------
/**
* This method is public as an implementation side effect and must not be called directly.
*/
public void startBuilding() {}
/**
* This method is public as an implementation side effect and must not be called directly.
*/
public void endBuilding() {}
public static List
* Command file parsing is done through the {@link #read(InputStream,CommandBuilder) read} method, which is
* the only way to interact with this class.
*
* Note that while this class knows how to read the content of a command XML file, its role is not to interpret it. This
* is done by instances of {@link CommandBuilder}.
*
* @see CommandsXmlConstants
* @see CommandBuilder
* @see CommandWriter
* @author Nicolas Rinaudo
*/
public class CommandReader extends DefaultHandler implements CommandsXmlConstants {
/** Where to send building messages. */
private final CommandBuilder builder;
/**
* Creates a new command reader.
* @param b where to send custom command events.
*/
private CommandReader(CommandBuilder b) {
builder = b;
}
/**
* Parses the content of the specified input stream.
*
* This method will go through the specified input stream and notify the builder of any new command declaration it
* encounters. Note that parsing is done in a very lenient fashion, and perfectly invalid XML files might not raise
* an exception. This is not a flaw in the parser, and both allows muCommander to be error resilient and the commands
* file format to be extended without having to rewrite most of this code.
*
* Note that even if an error occurs, both of the builder's {@link CommandBuilder#startBuilding()} and
* {@link CommandBuilder#endBuilding()} methods will still be called. Parsing will stop at the first error
* however, so while the builder is guaranteed to receive correct messages, it might not receive all declared
* commands.
*
* @param in where to read command data from.
* @param b where to send building events to.
* @throws IOException thrown if any error occurs.
*/
public static void read(InputStream in, CommandBuilder b) throws CommandException, IOException {
b.startBuilding();
try {
SAXParserFactory.newInstance().newSAXParser().parse(in, new CommandReader(b));
} catch(ParserConfigurationException | SAXException e) {
throw new CommandException(e);
} finally {
b.endBuilding();
}
}
/**
* This method is public as an implementation side effect and should not be called directly.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// New custom command declaration.
if (qName.equals(ELEMENT_COMMAND)) {
String alias = attributes.getValue(ATTRIBUTE_ALIAS);
String command = attributes.getValue(ATTRIBUTE_VALUE);
String fileMask = attributes.getValue(ATTRIBUTE_FILEMASK);
// Makes sure the required attributes are there.
if (alias != null && command != null) {
CommandType type = CommandType.parseCommandType(attributes.getValue(ATTRIBUTE_TYPE));
String display = attributes.getValue(ATTRIBUTE_DISPLAY);
// Creates the command and passes it to the builder.
try {
builder.addCommand(new Command(alias, command, type, display, fileMask));
} catch (CommandException e) {
throw new SAXException(e);
}
}
}
}
}
================================================
FILE: src/main/java/com/mucommander/command/CommandType.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* Note that this method is not strict in the arguments it receives:
*
*
* This interface is only meant as a convenient way of sharing the XML
* file format between the {@link com.mucommander.command.CommandWriter}
* and {@link CommandReader}. It will be removed at bytecode optimisation time.
*
* Commands XML files must match the following DTD:
*
* Events are triggered when:
* It is however not aware of modifications that are made to the contained objects themselves.
*
* @author Maxence Bernard
*/
public class AlteredVector Listeners are stored as weak references so {@link #removeVectorChangeListener(VectorChangeListener)}
* doesn't need to be called for listeners to be garbage collected when they're not used anymore.
*
* @param listener the VectorChangeListener to add to the list of registered listeners.
* @see #removeVectorChangeListener(VectorChangeListener)
*/
public void addVectorChangeListener(VectorChangeListener listener) {
listeners.put(listener, null);
}
/**
* Removes the specified VectorChangeListener from the list of registered listeners.
*
* @param listener the VectorChangeListener to remove from the list of registered listeners.
* @see #addVectorChangeListener(VectorChangeListener)
*/
public void removeVectorChangeListener(VectorChangeListener listener) {
listeners.remove(listener);
}
/**
* This method is called when one or more elements has been added to this AlteredVector to notify listeners.
*
* @param startIndex index at which the first element has been added
* @param nbAdded number of elements added
*/
private void fireElementsAddedEvent(int startIndex, int nbAdded) {
for (VectorChangeListener listener : listeners.keySet()) {
listener.elementsAdded(startIndex, nbAdded);
}
}
/**
* This method is called when one or more elements has been removed from this AlteredVector to notify listeners.
*
* @param startIndex index at which the first element has been removed
* @param nbRemoved number of elements removed
*/
private void fireElementsRemovedEvent(int startIndex, int nbRemoved) {
for (VectorChangeListener listener : listeners.keySet()) {
listener.elementsRemoved(startIndex, nbRemoved);
}
}
/**
* This method is called when an element has been changed in this AlteredVector to notify listeners.
*
* @param index index of the element that has been changed
*/
private void fireElementChangedEvent(int index) {
for (VectorChangeListener listener : listeners.keySet()) {
listener.elementChanged(index);
}
}
@Override
public void setElementAt(E o, int i) {
super.setElementAt(o, i);
fireElementChangedEvent(i);
}
@Override
public E set(int i, E o) {
o = super.set(i, o);
fireElementChangedEvent(i);
return o;
}
@Override
public void insertElementAt(E o, int i) {
super.insertElementAt(o, i);
fireElementsAddedEvent(i, 1);
}
@Override
public void add(int i, E o) {
insertElementAt(o, i);
fireElementsAddedEvent(i, 1);
}
@Override
public void addElement(E o) {
super.addElement(o);
fireElementsAddedEvent(size()-1, 1);
}
@Override
public boolean add(E o) {
addElement(o);
fireElementsAddedEvent(size()-1, 1);
return true;
}
@Override
public boolean addAll(Collection extends E> collection) {
int sizeBefore = size();
boolean b = super.addAll(collection);
fireElementsAddedEvent(sizeBefore, size()-sizeBefore);
return b;
}
@Override
public boolean addAll(int i, Collection extends E> collection) {
int sizeBefore = size();
boolean b = super.addAll(i, collection);
fireElementsAddedEvent(i, size()-sizeBefore);
return b;
}
@Override
public void removeElementAt(int i) {
super.removeElementAt(i);
fireElementsRemovedEvent(i, 1);
}
@Override
public E remove(int i) {
E o = super.remove(i);
fireElementsRemovedEvent(i, 1);
return o;
}
@Override
public boolean removeElement(Object o) {
int index = indexOf(o);
if(index==-1)
return false;
removeElementAt(index);
return true;
}
@Override
public boolean remove(Object o) {
return removeElement(o);
}
@Override
public void removeAllElements() {
int sizeBefore = size();
super.removeAllElements();
fireElementsRemovedEvent(0, sizeBefore);
}
@Override
public void clear() {
removeAllElements();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/collections/Enumerator.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Those classes need to be registered as listeners to receive those events, this can be done by calling
* {@link AlteredVector#addVectorChangeListener(VectorChangeListener)}.
*
* @author Maxence Bernard
*/
public interface VectorChangeListener {
/**
* This method is called when one or more elements has been added to the AlteredVector.
*
* @param startIndex index at which the first element has been added
* @param nbAdded number of elements added
*/
void elementsAdded(int startIndex, int nbAdded);
/**
* This method is called when one or more elements has been removed from the AlteredVector.
*
* @param startIndex index at which the first element has been removed
* @param nbRemoved number of elements removed
*/
void elementsRemoved(int startIndex, int nbRemoved);
/**
* This method is called when an element has been changed in the AlteredVector.
*
* @param index index of the element that has been changed
*/
void elementChanged(int index);
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/BufferedConfigurationExplorer.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* This behaves exactly as a {@link ConfigurationExplorer}, but keeps track of its own path. This is meant
* for instances of {@link Configuration} to prune empty branches.
*
* @author Nicolas Rinaudo
*/
class BufferedConfigurationExplorer extends ConfigurationExplorer {
/** Sections that have been passed through. */
private final Stack
* A
* Configuration variable names follow the same convention as Java System properties: a serie of strings
* separated by periods. By convention, all but the last string are called configuration sections, while
* the last one is the variable's name. When we refer to a variable's fully qualified name, we're talking
* about the whole period-separated name.
* While the
* By default, configuration data is assumed to be in the standard muCommander file format (described in
* {@link XmlConfigurationReader}). However, application writers can modify that to any format they want
* through the {@link #setReaderFactory(ConfigurationReaderFactory) setReaderFactory} and
* {@link #setWriterFactory(ConfigurationWriterFactory) setWriterFactory} methods.
*
*
* While
* Classes that need to monitor the state of the configuration in order, for example, to react to changes
* dynamically rather than wait for an application reboot can implement the {@link ConfigurationListener}
* interface and register themselves through
* {@link #addConfigurationListener(ConfigurationListener) addConfigurationListener}. This guarantees that they
* will receive configuration events whenever a modification occurs.
* The resulting instance will use default {@link XmlConfigurationReader readers} and
* {@link XmlConfigurationWriter writers}.
*
* Note that until the {@link #setSource(ConfigurationSource) setSource} method has been
* invoked, calls to read or write methods without a stream parameter will fail.
*/
public Configuration() {
}
/**
* Creates a new instance of
* The resulting instance will use the default {@link XmlConfigurationReader readers} and
* {@link XmlConfigurationWriter writers}.
*
* @param source where the resulting instance will look for its configuration data.
*/
public Configuration(ConfigurationSource source) {
setSource(source);
}
/**
* Creates a new instance of
* Note that until the {@link #setSource(ConfigurationSource) setSource} method has been
* invoked, calls to read or write methods without a stream parameter will fail.
*
* @param reader factory for configuration readers.
* @param writer factory for configuration writers.
*/
public Configuration(ConfigurationReaderFactory reader, ConfigurationWriterFactory writer) {
setReaderFactory(reader);
setWriterFactory(writer);
}
/**
* Creates a new instance of
* In order to reset the configuration to its default reader factory, application writers can call this method
* with a
* By default, this method will return an {@link XmlConfigurationReader XML reader} factory.
* This can be modified by calling {@link #setReaderFactory(ConfigurationReaderFactory) setReaderFactory}.
*
* @return the factory that is being used to create reader instances.
* @see #setReaderFactory(ConfigurationReaderFactory)
*/
private ConfigurationReaderFactory getReaderFactory() {
synchronized(readerLock) {
if (readerFactory == null) {
return XmlConfigurationReader.FACTORY;
}
return readerFactory;
}
}
/**
* Sets the factory that will be used to create writer instances.
*
* In order to reset the configuration to its default writer factory, application writers can call
* this method will a
* By default, this method will return an {@link XmlConfigurationWriter} factory. However, this
* can be modified by calling {@link #setWriterFactory(ConfigurationWriterFactory) setWriterFactory}.
*
* @return the factory that is being used to create writer instances.
* @see #setWriterFactory(ConfigurationWriterFactory)
*/
private ConfigurationWriterFactory getWriterFactory() {
synchronized(writerLock) {
if (writerFactory == null) {
return XmlConfigurationWriter.FACTORY;
}
return writerFactory;
}
}
/**
* Loads configuration from the specified input stream, using the specified configuration reader.
* @param in where to read the configuration from.
* @param reader reader that will be used to interpret the content of
* This method will use the configuration reader set by {@link #setReaderFactory(ConfigurationReaderFactory)} if any,
* or an {@link XmlConfigurationReader} instance if not.
*
* @param in where to read the configuration from.
* @throws ConfigurationException if a configuration error occurs.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @throws ReaderConfigurationException if the {@link ConfigurationReaderFactory} isn't properly configured.
* @throws IOException if an I/O error occurs.
* @see #read()
* @see #read(ConfigurationReader)
* @see #read(Reader,ConfigurationReader)
* @deprecated Application developers should use {@link #read(Reader)} instead. This method assumes the specified
* {@link InputStream} to be
* This method will use the configuration reader set by {@link #setReaderFactory(ConfigurationReaderFactory)} if any,
* or an {@link XmlConfigurationReader} instance if not.
*
* @param in where to read the configuration from.
* @throws ConfigurationException if a configuration error occurs.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @throws ReaderConfigurationException if the {@link ConfigurationReaderFactory} isn't properly configured.
* @throws IOException if an I/O error occurs.
* @see #read()
* @see #read(ConfigurationReader)
* @see #read(Reader,ConfigurationReader)
*/
public void read(Reader in) throws ConfigurationException, IOException {
read(in, getReaderFactory().getReaderInstance());
}
/**
* Loads configuration using the specified configuration reader.
*
* This method will use the input stream provided by {@link #setSource(ConfigurationSource)} if any, or
* fail otherwise.
*
* @param reader reader that will be used to interpret the content of
* If a reader has been specified through {@link #setReaderFactory(ConfigurationReaderFactory)}, it
* will be used to analyse the configuration. Otherwise, an {@link XmlConfigurationReader}
* instance will be used.
*
*
* If a configuration source has been specified through {@link #setSource(ConfigurationSource)}, it will be
* used. Otherwise, this method will fail.
*
* @throws IOException if an I/O error occurs.
* @throws ConfigurationException if a configuration error occurs.
* @throws SourceConfigurationException if no {@link ConfigurationSource} hasn't been set.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @throws ReaderConfigurationException if the {@link ConfigurationReaderFactory} isn't properly configured.
* @see #write()
* @see #read(InputStream)
* @see #read(ConfigurationReader)
* @see #read(Reader,ConfigurationReader)
*/
public void read() throws ConfigurationException, IOException {
read(getReaderFactory().getReaderInstance());
}
/**
* Writes the configuration to the specified {@link Writer}.
*
* This method will use {@link #getWriterFactory()} to create instances of configuration writer.
*
* @param out where to write the configuration to.
* @throws ConfigurationException if any error occurs.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @throws WriterConfigurationException if the {@link ConfigurationWriterFactory} isn't properly configured.
* @see #read(InputStream)
* @see #write()
*/
public void write(Writer out) throws ConfigurationException {
write(getWriterFactory().getWriterInstance(out));
}
/**
* Writes the configuration.
*
* If a configuration source was specified through {@link #setSource(ConfigurationSource)}, it will be used
* to open an output stream. Otherwise, this method will fail.
*
* @throws ConfigurationException if any error occurs.
* @throws SourceConfigurationException if no {@link ConfigurationSource} has been set.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @throws IOException if any I/O error occurs.
* @see #read(ConfigurationReader)
* @see #write()
*/
public void write() throws IOException, ConfigurationException {
ConfigurationSource source = getSource(); // Configuration source.
// Makes sure the source has been set.
if (source == null) {
throw new SourceConfigurationException("No configuration source has been set");
}
try (Writer out = source.getWriter()) {
write(out);
}
}
/**
* Writes the configuration data to the specified builder.
* @param builder object that will receive configuration building messages.
* @throws ConfigurationException if any error occurs while going through the configuration tree.
*/
public void write(ConfigurationBuilder builder) throws ConfigurationException {
builder.startConfiguration();
build(builder, root);
builder.endConfiguration();
}
/**
* Recursively explores the specified section and invokes the specified builder's callback methods.
* @param builder object that will receive building events.
* @param root section to explore.
* @throws ConfigurationException if any error occurs.
*/
private synchronized void build(ConfigurationBuilder builder, ConfigurationSection root) throws ConfigurationException {
// Explores the section's variables.
Set
* At the end of this call,
* This method might trigger as many as two {@link ConfigurationEvent events}:
*
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed to all
* LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @param separator string used to separate each element of the list.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* This method will return
* If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed
* to all LISTENERS.
*
* @param name fully qualified name of the variable to set.
* @param value new value for the variable.
* @return
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @param separator character used to split the variable's value into a list.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to
* all registered LISTENERS.
*
* @param name name of the variable to remove.
* @return the variable's old value, or
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If the variable isn't set, this method will set it to
* If a class needs to be informed of the logical structure of a configuration instance,
* it implements this interface and registers an instance with the {@link Configuration} using
* its {@link Configuration#write(ConfigurationBuilder) build} method. The {@link Configuration}
* uses the instance to report configuration related events such as the start of sections and
* variable declarations.
*
*
* The
* This method will only be invoked once, before any other method in this interface.
*
* @throws ConfigurationException any Configuration error, possibly wrapping another exception.
*/
void startConfiguration() throws ConfigurationException;
/**
* Receives notification at the end of the configuration.
*
* This method will be invoked at most once, and if it is, it will be the last one. If an
* unrecoverable error happens, this method might never be called.
*
* @throws ConfigurationException any Configuration error, possibly wrapping another exception.
*/
void endConfiguration() throws ConfigurationException;
/**
* Receives notification at the beginning of a section.
*
* This method will be invoked once at the beginning of every configuration section. Unless an
* unrecoverable error happens, there will be an {@link #endSection(String) endSection} event for every
*
* This method will be invoked once at the end of every configuration section. There will be a
* corresponding {@link #startSection(String) startSection} event for every
* This method will be invoked once per variable found in a section. The declared variable
* will always belong to the section defined in the last {@link #startSection(String) startSection}
* event which hasn't yet been closed by an {@link #endSection(String) endSection} event. If there is
* no such section, the variable belongs to the unnamed root section.
*
* @param name name of the new variable.
* @param value value of the new variable.
* @throws ConfigurationException any Configuration error, possibly wrapping another exception.
*/
void addVariable(String name, String value) throws ConfigurationException;
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationEvent.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* The event is passed to every {@link ConfigurationListener} object which registered to receive such events using the
* {@link Configuration}'s {@link Configuration#addConfigurationListener(ConfigurationListener) addConfigurationListener} method.
* Each such listener object gets this
* The event will describe a modification of variable
*
* The returned value will be the variable's fully qualified name. If, for example, the
* modified variable is
* If the variable has been deleted, this method will return
* If the variable has been deleted, this method will return 0.
*
* @return the new value for the modified variable.
* @throws NumberFormatException if {@link #getValue()} cannot be cast as an integer.
*/
public int getIntegerValue() throws NumberFormatException {
return ConfigurationSection.getIntegerValue(value);
}
/**
* Returns the new value for the modified variable cast as a float.
*
* If the variable has been deleted, this method will return 0.
*
* @return the new value for the modified variable.
* @throws NumberFormatException if {@link #getValue()} cannot be cast as a float
*/
public float getFloatValue() throws NumberFormatException {
return ConfigurationSection.getFloatValue(value);
}
/**
* Returns the new value for the modified variable cast as a boolean.
*
* If the variable has been deleted, this method will return
* If the variable has been deleted, this method will return 0.
*
* @return the new value for the modified variable.
* @throws NumberFormatException if {@link #getValue()} cannot be cast as a long.
*/
public long getLongValue() throws NumberFormatException {
return ConfigurationSection.getLongValue(value);
}
/**
* Returns the new value for the modified variable cast as a double.
*
* If the variable has been deleted, this method will return 0.
*
* @return the new value for the modified variable.
* @throws NumberFormatException if {@link #getValue()} cannot be cast as a double.
*/
public double getDoubleValue() {
return ConfigurationSection.getDoubleValue(value);
}
/**
* Returns the new value for the modified variable cast as a {@link ValueList}.
*
* If the variable has been deleted, this method will return null.
*
* @param separator string used to tokenise the variable's value.
* @return the new value for the modified variable.
*/
public ValueList getListValue(String separator) {
return ConfigurationSection.getListValue(value, separator);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* This class can contain basic error information from either the
* If the application needs to pass through other types of exceptions, it must wrap them in a
*
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* Within the scope of the
* This exception is mostly meant to be used by implementations of {@link ConfigurationReader},
* as they're the ones who will analyse the syntax of a configuration stream.
*
* When applicable, instances of
* Since
* {@link #UNKNOWN_LOCATION} is a legal value for both
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* {@link #UNKNOWN_LOCATION} is a legal value for both
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* {@link #UNKNOWN_LOCATION} is a legal value for both
* By convention, the line at which an error occurs is equal to the number of line breaks encountered
* before the problem, plus one. This means that a line number of
* By convention, the column at which an error occurs is equal to the number of character encountered
* after the last line break and before the problem, plus one. This means that a column number of
*
* Implementations of this interface can register themselves to a {@link Configuration configuration} instance through
* its {@link Configuration#addConfigurationListener(ConfigurationListener) addConfigurationListener} method to be
* notified of configuration changes.
*
* @author Nicolas Rinaudo
*/
public interface ConfigurationListener {
/**
* Invoked when the configuration changes.
* @param event describes the configuration modification.
*/
void configurationChanged(ConfigurationEvent event);
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationReader.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* Application writers that need to implement a specific configuration format need to subclass this.
* Reader implementations have the task of parsing an input stream for configuration data and invoking the
* relevant callback methods of {@link ConfigurationBuilder}.
*
* The
* In order for an implementation of
* In addition, most readers will have an associated writer used to write configuration files in a
* format that the reader will understand.
*
* @author Nicolas Rinaudo
* @see ConfigurationReaderFactory
*/
public interface ConfigurationReader {
/**
* Reads configuration information from the specified input stream and invokes the specified builder's callback methods.
*
* When applicable, this method is expected to throw {@link ConfigurationFormatException format} exceptions rather
* than {@link ConfigurationException configuration} exceptions. This will allow applications to report errors in a
* way that is useful for users.
*
* @param in where to read the configuration information from.
* @param builder where to send configuration messages to.
* @throws IOException if an I/O error occurs.
* @throws ConfigurationException if another type of error occurs, in which case that error must be returned by
* A
* If
* Note that the order in which variable names are returned needs not be that in which they were added to the
* section. Callers should not rely on the order being consistent over time.
*
* @return an iterator on the names of the variables that are defined in the section.
*/
public Set
* If
* If
* If
* If
* If
* If
* If a subsection with the specified name already exists, it will be returned.
*
* @param name name of the new section.
* @return the subsection with the specified name.
*/
public ConfigurationSection addSection(String name) {
ConfigurationSection section = getSection(name);
// The section already exists, returns it.
if (section != null) {
return section;
}
// Creates the new section.
section = new ConfigurationSection();
sections.put(name, section);
return section;
}
/**
* Deletes the specified section.
* @param name name of the section to delete.
* @return the section that was deleted if any,
* Note that this method is very inefficient and should only be called when strictly necessary.
*
* @param section section to remove.
* @return
* Note that the order in which section names are returned needs not be that in which they were added to the
* section. Callers should not rely on the order being consistent over time.
*
* @return an enumeration on all of this section's subsections' names.
*/
public Set
* This method is meant for {@link Configuration} instances to prune dead branches.
*
* @return
* Application writers that need to retrieve configuration data from a non-standard source (over the network, from a
* database, ...) need to subclass this.
*
* Implementations of this interface can be registered through {@link Configuration}'s
* {@link Configuration#setSource(ConfigurationSource) setSource} method. Their purpose is
* to provide the system with streams to a configuration source. This system allows applications
* to retrieve their configuration information from non-standard sources, such as over the network,
* in a database, ...
*
* The
* Within the scope of the
* This exception is mostly meant to be used by implementations of {@link ConfigurationBuilder},
* as they have to analyse the structure of the configuration they're receiving events for.
*
* Since
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* A
* The returned builder instance will serialize configuration events to the specified writer.
*
* @param out where to write the configuration data.
* @return an instance of {@link ConfigurationBuilder}.
* @throws WriterConfigurationException if the factory wasn't properly configured.
*/
public abstract T getWriterInstance(Writer out) throws WriterConfigurationException;
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/DefaultConfigurationBuilder.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* This class is available as a convenience for applications that need to explore the content
* of a configuration tree. It provides no-op implementations for the methods defined in
* {@link ConfigurationBuilder}, and application writers can override the ones that are of use
* to them and ignore the others.
*
* @author Nicolas Rinaudo
*/
public class DefaultConfigurationBuilder implements ConfigurationBuilder {
/**
* Receive notification at the beginning of the configuration.
*
* By default, do nothing. Application writers may override this method in a subclass to take
* specific actions at the beginning of a document (such as allocating the root node of a tree
* or creating an output file).
*
*/
public void startConfiguration() {}
/**
* Receive notification at the end of the configuration.
*
* By default, do nothing. Application writers may override this method in a subclass to take
* specific actions at the end of a document (such as finalising a tree or closing an output file).
*
*/
public void endConfiguration() {}
/**
* Receive notification at the beginning of a section.
*
* By default, do nothing. Application writers may override this method in a subclass to take
* specific actions at the start of each element (such as allocating a new tree node or writing
* output to a file).
*
* @param name name of the new section.
*/
public void startSection(String name) {}
/**
* Receive notification at the end of a section.
*
* By default, do nothing. Application writers may override this method in a subclass to take
* specific actions at the end of each element (such as finalising a tree node or writing output
* to a file).
*
* @param name name of the finished section.
*/
public void endSection(String name) {}
/**
* Receive notification of variable definition.
*
* By default, do nothing. Application writers may override this method to take specific actions for
* each variable definition (such as adding a leaf to a tree node, or printing it to a file).
*
* @param name name of the new variable.
* @param value value of the new variable.
*/
public void addVariable(String name, String value) {}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/FileConfigurationSource.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* This exception is mostly meant to be used by implementations of {@link ConfigurationReaderFactory},
* as they're the ones who will configure instances of {@link ConfigurationReader}.
*
* Since
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* This exception is meant to be thrown by {@link Configuration} whenever a method that requires a
* {@link ConfigurationSource} to have been set is called.
*
* Since
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* Instances of this class can only be retrieved through {@link ValueList#valueIterator()}.
*
* @author Nicolas Rinaudo
*/
public class ValueIterator implements Iterator
* Such values will simply be split using a
* In addition to the regular
* This exception is mostly meant to be used by implementations of writer,
* as they're the ones who will configure instances of {@link ConfigurationBuilder}.
*
* Since
* The existing exception will be embedded in the new one, and its message will
* become the default message for the
* The existing exception will be embedded in the new one, but the new exception will have its own message.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a
* The format of XML files parsed by instances of
* For example:
*
* Information on the XML file format can be found {@link XmlConfigurationReader here}.
*
* @author Nicolas Rinaudo
*/
public class XmlConfigurationWriter implements ConfigurationBuilder {
/** Factory used to create instances of {@link XmlConfigurationWriter}. */
public static final ConfigurationWriterFactory
* Configuration data is stored as a set of variables organised in sections. A typical variable
* name is:
* Configuration data is stored in instances of {@link com.mucommander.commons.conf.Configuration}, which offers a set
* of methods manipulate variables:
*
* The
* The default configuration format is described in {@link com.mucommander.commons.conf.XmlConfigurationReader}.
* Application writers who wish to change this can do so by:
*
* Classes that need to be notified when the configuration has changed can do so by:
*
*
* This class is abstract (as the name implies) and implemented by two subclasses:
*
* The first time one of the Files returned by the Note that an instance of Important note: the given path's separator character must be '/' and the path must be relative to the
* archive's root, i.e. not start with a leading '/', otherwise the entry will not be found.
*
* @param entryPath path to an entry within this archive
* @return an AbstractFile that corresponds to the given entry path
* @throws IOException if neither the entry nor its parent exist within the archive
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
public AbstractFile getArchiveEntryFile(String entryPath) throws IOException {
// Make sure the entries tree is created and up-to-date
checkEntriesTree();
// Todo: check if that's really necessary / if there is a way to remove this
entryPath = entryPath.replace(File.separatorChar, ArchiveEntry.SEPARATOR_CHAR);
// Find the entry node corresponding to the given path
DefaultMutableTreeNode entryNode = entryTreeRoot.findEntryNode(entryPath);
if(entryNode==null) {
int depth = ArchiveEntry.getDepth(entryPath);
AbstractFile parentFile;
if(depth==1)
parentFile = this;
else {
String parentPath = entryPath;
if(parentPath.endsWith("/"))
parentPath = parentPath.substring(0, parentPath.length()-1);
parentPath = parentPath.substring(0, parentPath.lastIndexOf('/'));
parentFile = getArchiveEntryFile(parentPath);
if(parentFile==null) // neither the entry nor the parent exist
throw new IOException();
}
return getArchiveEntryFile(new ArchiveEntry(entryPath, false, 0, 0, false), parentFile);
}
return getArchiveEntryFile(entryNode);
}
/**
* Creates and returns an {@link AbstractFile} instance corresponding to the given entry node.
* This method recurses to resolve the entry's parent file.
*
* @param entryNode tree node corresponding to the entry for which to return a file
* @return an {@link AbstractFile} instance corresponding to the given entry node
*/
private AbstractFile getArchiveEntryFile(DefaultMutableTreeNode entryNode) throws IOException {
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)entryNode.getParent();
return getArchiveEntryFile(
(ArchiveEntry)entryNode.getUserObject(),
parentNode == entryTreeRoot ? this : getArchiveEntryFile(parentNode)
);
}
/**
* Returns an iterator of {@link ArchiveEntry} that iterates through all the entries of this archive.
* Implementations of this method should as much as possible return entries in their "natural order", i.e. the order
* in which they are stored in the archive.
*
* This method is called the first time one of the
* This method is implemented by {@link com.mucommander.commons.file.AbstractROArchiveFile} and
* {@link com.mucommander.commons.file.AbstractRWArchiveFile} to respectively return AbstractFile classes should never be instantiated directly. Instead, the {@link FileFactory} The returned It is important to note that this method is provided for interoperability purposes, for the sole purpose of
* connecting to APIs that require a The returned name is the filename extracted from this file's This method should be overridden if a special processing (e.g. URL-decoding) needs to be applied to the
* returned filename.
*
* @return this file's name
*/
public String getName() {
String name = fileURL.getFilename();
// If filename is null, use host instead
if (name == null) {
name = fileURL.getHost();
// If host is null, return an empty string
if (name == null) {
return "";
}
}
return name;
}
/**
* Returns this file's extension,
* A filename has an extension if and only if:
* This default implementation returns the string representation of this file's {@link #getURL() url}, without
* the login and password parts. File implementations overriding this method should always return a path free of
* any login and password, so that it can safely be displayed to the end user or stored, without risking to
* compromise sensitive information.
*
*
* @return the absolute path to this file
*/
public String getAbsolutePath() {
return getURL().toString(false);
}
/**
* Returns the canonical path to this file, resolving any symbolic links or '..' and '.' occurrences.
*
* This implementation simply returns the value of {@link #getAbsolutePath()}, and thus should be overridden
* if canonical path resolution is available.
*
* @return the canonical path to this file
*/
public String getCanonicalPath() {
return getAbsolutePath();
}
/**
* Returns an This default implementation returns the default separator "/", this method should be overridden if the path
* separator used by the file implementation is different.
*
* @return the path separator used by this file
*/
public String getSeparator() {
return DEFAULT_SEPARATOR;
}
/**
* Returns This default implementation is solely based on the filename and returns
* This default implementation returns the file whose URL has the same scheme as this one, same credentials (if any),
* and a path equal to
* This default implementation returns
* The notion of volume may or may not have a meaning depending on the kind of filesystem. On local filesystems,
* the notion of volume can be assimilated into that of mount point for UNIX-based OSes, or drive
* for the Windows platform. Volumes may also have a meaning for certain network filesystems such as SMB, for which
* shares can be considered as volumes. Filesystems that don't have a notion of volume should return the
* {@link #getRoot() root folder}.
*
* This default implementation returns this file's {@link #getRoot() root folder}. This method should be overridden
* if this is not adequate.
*
* @return the volume on which this file is located.
*/
public AbstractFile getVolume() {
return getRoot();
}
/**
* Returns an This implementation starts by checking whether the {@link FileOperation#RANDOM_READ_FILE} operation is
* supported or not.
* If it is, a {@link #getRandomAccessInputStream() random input stream} to this file is retrieved and used to seek
* to the specified offset. If it's not, a regular {@link #getInputStream() input stream} is retrieved, and
* {@link java.io.InputStream#skip(long)} is used to position the stream to the specified offset, which on most
* This method should be overridden by filesystems that do not offer a {@link #getOutputStream()}
* implementation, but that can take an The Read and write operations are buffered, with a buffer of {@link #IO_BUFFER_SIZE} bytes. For performance
* reasons, this buffer is provided by {@link BufferPool}. Thus, there is no need to surround the InputStream
* with a {@link java.io.BufferedInputStream}.
*
* Copy progress can optionally be monitored by supplying a {@link com.mucommander.commons.io.CounterInputStream}.
*
* @param in the InputStream to read from
* @param append if true, data written to the OutputStream will be appended to the end of this file. If false, any
* existing data will be overwritten.
* @param length length of the stream before EOF is reached, This method throws an {@link IOException} if the operation failed, for any of the following reasons:
* If this file supports the {@link FileOperation#COPY_REMOTELY} file operation, an attempt to perform a
* {@link #copyRemotelyTo(AbstractFile) remote copy} of the file to the destination is made. If the operation isn't
* supported or wasn't successful, the file is copied manually, by transferring its contents to the destination
* using {@link #copyRecursively(AbstractFile, AbstractFile)}. This method throws an {@link IOException} if the operation failed, for any of the following reasons:
* If this file supports the {@link FileOperation#RENAME} file operation, an attempt to
* {@link #renameTo(AbstractFile) rename} the file to the destination is made. If the operation isn't supported
* or wasn't successful, the file is moved manually, by transferring its contents to the destination using
* {@link #copyTo(AbstractFile)} and then deleting the source. This generic implementation simply creates a zero-byte file. {@link AbstractRWArchiveFile} implementations
* may want to override this method so that it creates a valid archive with no entry. To illustrate, an empty Zip
* file with proper headers is 22-byte long.
*
* @throws IOException if the file could not be created, either because it already exists or because of an I/O error
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public void mkfile() throws IOException {
if (exists()) {
throw new IOException();
}
if (isFileOperationSupported(FileOperation.WRITE_FILE)) {
getOutputStream().close();
} else {
copyStream(new ByteArrayInputStream(new byte[]{}), false, 0);
}
}
/**
* Returns the children files that this file contains, filtering out files that do not match the specified FileFilter.
* For this operation to be successful, this file must be 'browsable', i.e. {@link #isBrowsable()} must return
* This default implementation filters out files *after* they have been created. This method
* should be overridden if a more efficient implementation can be provided by subclasses.
*
* @param filter the FilenameFilter to be used to filter out files from the list, may be Implementation note: the default implementation of this method calls sequentially {@link #changePermission(int, int, boolean)},
* for each permission and access (that's a total 9 calls). This may affect performance on filesystems which need
* to perform an I/O request to change each permission individually. In that case, and if the filesystem allows
* to change all permissions at once, this method should be overridden.
*
* @param permissions new permissions for this file
* @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because
* of an I/O error.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public void changePermissions(int permissions) throws IOException {
int bitShift = 0;
PermissionBits mask = getChangeablePermissions();
for (int a = OTHER_ACCESS; a <= USER_ACCESS; a++) {
for (int p = EXECUTE_PERMISSION; p <= READ_PERMISSION; p = p<<1) {
if (mask.getBitValue(a, p)) {
changePermission(a, p, (permissions & (1 << bitShift)) != 0);
}
bitShift++;
}
}
}
/**
* Returns a string representation of this file's permissions.
*
* The first character is 'l' if this file is a symbolic link,'d' if it is a directory, '-' otherwise. Then
* the string contains up to 3 character triplets, for each of the 'user', 'group' and 'other' access types, each
* containing the following characters:
* The first character triplet for 'user' access will always be added to the permissions. Then the 'group' and
* 'other' triplets will only be added if at least one of the user permission bits is supported, as tested with
* this file's permissions mask.
* Here are a couple examples to illustrate:
*
* Note that even if A filename has an extension if and only if: Although {@link #getChild} can be used to retrieve a direct child file, this method should be favored because
* it allows to use this file instance as the parent of the returned child file.
*
* @param filename the name of the child file to be created
* @return an AbstractFile representing the requested direct child file, never null
* @throws IOException in any of the cases listed above
*/
public final AbstractFile getDirectChild(String filename) throws IOException {
if (filename.contains(getSeparator())) {
throw new IOException();
}
AbstractFile childFile = getChild(filename);
// Use this file as the child's parent, it avoids creating a new AbstractFile instance when getParent() is called
childFile.setParent(this);
return childFile;
}
/**
* Convenience method that creates a directory as a direct child of this directory.
* This method will fail if this file is not a directory.
*
* @param name name of the directory to create
* @throws IOException if the directory could not be created, either because the file already exists or for any
* other reason.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final void mkdir(String name) throws IOException {
getChild(name).mkdir();
}
/**
* Creates this file as a directory and any parent directory that does not already exist. This method will fail
* (throw an
* Important note: the returned {@link AbstractArchiveFile}, if any, may not necessarily be an
* archive, as specified by {@link #isArchive()}. This is the case for files that were resolved as
* {@link AbstractArchiveFile} instances based on their path, but that do not yet exist or were created as
* directories. On the contrary, an existing archive will necessarily return a non-null value.
*
* @return the parent {@link AbstractArchiveFile} that contains this file
*/
public final AbstractArchiveFile getParentArchive() {
if (hasAncestor(AbstractArchiveFile.class)) {
return getAncestor(AbstractArchiveFile.class);
} else if (hasAncestor(AbstractArchiveEntryFile.class)) {
AbstractArchiveEntryFile ancestor = getAncestor(AbstractArchiveEntryFile.class);
return ancestor != null ? ancestor.getArchiveFile() : null;
}
return null;
}
/**
* Returns an icon representing this file, using the default {@link com.mucommander.commons.file.icon.FileIconProvider}
* registered in {@link FileFactory}. The specified preferred resolution will be used as a hint, but the returned
* icon may have different dimension; see {@link com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)}
* for full details.
* This method may return The checksum is returned as an hexadecimal string, such as "6d75636f0a". The length of this string depends on
* the kind of algorithm.
*
* Note: this method does not reset the The checksum is returned as an hexadecimal string, such as "6d75636f0a". The length of this string depends on
* the kind of Note: this method does not reset the A filename has an extension if and only if:
* The returned extension (if any) is free of any extension separator character ( A filename has an extension if and only if: Important: this method does not close the
* Unlike {@link #equalsCanonical(Object)}, this method is not allowed to perform I/O operations and block
* the caller thread.
*
* @param o the object to compare against this instance
* @return Returns It is noteworthy that this method uses It is also worth noting that hostnames are not resolved, which means this method does not consider
* a hostname and its corresponding IP address as being equal.
*
* Unlike {@link #equals(Object)}, this method is allowed to perform I/O operations and block
* the caller thread.
*
* @param o the object to compare against this instance
* @return This {@link FileOperation#CHANGE_DATE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @param lastModified last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
* @throws IOException if the date couldn't be changed, either because of insufficient permissions or because of
* an I/O error.
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract void setLastModifiedDate(long lastModified) throws IOException;
public abstract void changeReplication(short replication) throws IOException;
/**
* Returns this file's size in bytes, This method may return permissions for which none of the bits are supported, but may never return
* This {@link FileOperation#CHANGE_PERMISSION file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @param access see {@link PermissionTypes} for allowed values
* @param permission see {@link PermissionAccesses} for allowed values
* @param enabled true to enable the flag, false to disable it
* @throws IOException if the permission couldn't be changed, either because of insufficient permissions or because
* of an I/O error.
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
* @see #getChangeablePermissions()
*/
public abstract void changePermission(int access, int permission, boolean enabled) throws IOException;
/**
* Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.
* It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').
* If the owner information is not available to the
* An archive is a file container that can be {@link #isBrowsable() browsed}. Archive files may not be
* {@link #isDirectory() directories}, and vice-versa.
*
* @return This {@link FileOperation#LIST_CHILDREN file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return the children files that this file contains
* @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract AbstractFile[] ls() throws IOException;
/**
* Creates this file as a directory. This method will fail (throw an This {@link FileOperation#CREATE_DIRECTORY file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @throws IOException if the directory could not be created, either because this file already exists or for any
* other reason.
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract void mkdir() throws IOException;
/**
* Returns an This {@link FileOperation#READ_FILE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return an
* This method may throw an This {@link FileOperation#WRITE_FILE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return an
* This method may throw an This {@link FileOperation#APPEND_FILE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return an This {@link FileOperation#RANDOM_READ_FILE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return a This {@link FileOperation#RANDOM_WRITE_FILE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return a This {@link FileOperation#DELETE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @throws IOException if this file does not exist or could not be deleted
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract void delete() throws IOException;
/**
* Renames this file to a specified destination file, overwriting the destination if it exists. If this file is a
* directory, any file or directory it contains will also be moved.
* After normal completion, this file will not exist anymore: {@link #exists()} will return This method throws an {@link IOException} if the operation failed, for any of the following reasons:
* This {@link FileOperation#RENAME file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @param destFile file to rename this file to
* @throws IOException in any of the error cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract void renameTo(AbstractFile destFile) throws IOException;
/**
* Remotely copies this file to a specified destination file, overwriting the destination if it exists.
* If this file is a directory, any file or directory it contains will also be copied.
*
* This method differs from {@link #copyTo(AbstractFile)} in that it performs a server-to-server copy of the
* file(s), without having the file's contents go through to the local process. This operation should only be
* implemented if it offers a performance advantage over a regular client-driven copy like
* {@link #copyTo(AbstractFile)}, or if {@link FileOperation#WRITE_FILE} is not supported (output streams cannot be
* retrieved) and thus a regular copy cannot succeed.
*
* This method throws an {@link IOException} if the operation failed, for any of the following reasons:
* The behavior in the case of an error occurring in the midst of the transfer is unspecified: files that have
* been copied (even partially) may or may not be left in the destination.
*
* @param destFile the destination file to copy this file to
* @throws IOException in any of the error cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract void copyRemotelyTo(AbstractFile destFile) throws IOException;
/**
* Returns the free space (in bytes) on the disk/volume where this file is, This {@link FileOperation#GET_FREE_SPACE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return the free space (in bytes) on the disk/volume where this file is, This {@link FileOperation#GET_TOTAL_SPACE file operation} may or may not be supported by the underlying filesystem
* -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't
* supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.
*
* @return the total space (in bytes) of the disk/volume where this file is
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract long getTotalSpace() throws IOException;
/**
* Returns the file Object of the underlying API providing access to the filesystem. The returned Object may expose
* filesystem-specific functionality that are not available in If the implemented filesystem has no such Object, It's possible to modify this loader's classpath at runtime through the {@link #addFile(AbstractFile)} method.
*
* @author Nicolas Rinaudo
*/
public class AbstractFileClassLoader extends ClassLoader {
private final static org.slf4j.Logger LOGGER = null;//org.slf4j.LoggerFactory.getLogger(AbstractFileClassLoader.class);
/** All abstract files in which to look for classes and resources. */
private final Vector
* Note that the file will not be added if it's already in the classpath.
*
* @param file file to add the class loader's classpath.
* @throws IllegalArgumentException if
* This methods can be used to update the entry's date and permissions for instance.
*
* @param entry the entry to update in the archive
* @throws IOException if the entry doesn't exist in the archive or if an I/O error occurs
* @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by
* the underlying file protocol.
*/
public abstract void updateEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;
/**
* Processes the archive file to leave it in an optimal form. This method should be called after a writable archive
* has been modified (entries added or removed).
*
* The actual effect of this method on the archive file depends on the kind of archive. It may be implemented
* as a no-op if there is no use for it.
* To illustrate, in the case of a {@link com.mucommander.commons.file.impl.zip.ZipArchiveFile}, this method removes chunks
* of free space that are left when entries are deleted.
*
* @throws IOException if an I/O error occurs
* @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by
* the underlying file protocol.
*/
public abstract void optimizeArchive() throws IOException, UnsupportedFileOperationException;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ArchiveEntry.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Important: the path of archive entries must use the '/' character as a path delimiter, and be relative
* to the archive's root, i.e. must not start with a leading '/'.
*
* @author Maxence Bernard
*/
public class ArchiveEntry extends SimpleFileAttributes {
public static final char SEPARATOR_CHAR = '/';
private static final String SEPARATOR_STRING = String.valueOf(SEPARATOR_CHAR);
/** Encapsulated entry object */
private Object entryObject;
/** Caches the computed hashcode */
private int hashCode;
/**
* Creates a new ArchiveEntry with all attributes set to their default value.
*/
public ArchiveEntry() {
}
/**
* Creates a new ArchiveEntry using the values of the supplied attributes.
*
* @param path the entry's path with {@link #SEPARATOR_CHAR} as path delimiter
* @param directory true if the entry is a directory
* @param date the entry's date
* @param size the entry's size
* @param exists Important note: the given path's separator character must be '/' and the path must be relative to the
* archive's root, i.e. not start with a leading '/', otherwise the entry will not be found. Trailing separators
* are ignored when paths are compared, for example the path 'temp' will match the entry 'temp/'.
*
* @param entryPath the path to the entry to look up in this tree
* @return the node that corresponds to the specified entry path
*/
DefaultMutableTreeNode findEntryNode(String entryPath) {
int entryDepth = ArchiveEntry.getDepth(entryPath);
int slashPos = 0;
DefaultMutableTreeNode currentNode = this;
for (int d = 1; d <= entryDepth; d++) {
String subPath = d == entryDepth ?
entryPath :
entryPath.substring(0, (slashPos = entryPath.indexOf('/', slashPos)+1));
DefaultMutableTreeNode matchNode = getDefaultMutableTreeNode(currentNode, subPath);
if (matchNode == null) {
return null; // No node matching the provided path, return null
}
currentNode = matchNode;
}
return currentNode;
}
@Nullable
private DefaultMutableTreeNode getDefaultMutableTreeNode(DefaultMutableTreeNode currentNode, String subPath) {
int nbChildren = currentNode.getChildCount();
for (int c = 0; c < nbChildren; c++) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)currentNode.getChildAt(c);
// Path comparison is 'trailing slash insensitive'
if (PathUtils.pathEquals(((ArchiveEntry)childNode.getUserObject()).getPath(), subPath, "/")) {
// Found the node, let's return it
return childNode;
}
}
return null;
}
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(ArchiveEntryTree.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ArchiveFormatProvider.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* For {@link AbstractArchiveFile} implementations to be automatically instantiated by {@link FileFactory},
* this interface needs to be implemented and an instance registered with {@link FileFactory}.
*
* @author Nicolas Rinaudo, Maxence Bernard
* @see AbstractArchiveFile
* @see FileFactory
*/
public interface ArchiveFormatProvider {
/**
* Creates a new instance of
* A typical implementation of {@link #authenticate(FileURL)} will look for {@link Credentials} matching the
* specified URL and, if one set (or more) is found, call {@link FileURL#setCredentials(Credentials)} to set them.
* Likewise, this method may also look for and set {@link FileURL#setProperty(String, String) URL properties},
* that will be used by the corresponding {@link AbstractFile} during or after resolution.
*
* {@link #authenticate(FileURL)} should normally be called only for {@link FileURL} schemes that
* {@link FileURL#getAuthenticationType() support authentication}. Implementations should however not rely on that and
* handle non-authenticated URLs as a no-op.
*
* A default authenticator can be registered at {@link FileFactory#setDefaultAuthenticator(Authenticator)}.
*
* @see FileURL#getAuthenticationType()
* @see FileFactory#setDefaultAuthenticator(Authenticator)
* @see FileFactory#getFile(FileURL, AbstractFile, Authenticator, Object...)
* @author Maxence Bernard
*/
public interface Authenticator {
/**
* Authenticates the specified {@link FileURL} instance.
*
* @param fileURL the file URL to authenticate
*/
void authenticate(FileURL fileURL);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/Credentials.java
================================================
package com.mucommander.commons.file;
/**
* This class is a container for a login and password pair, used to authenticate a location on a filesystem.
*
* @see com.mucommander.commons.file.FileURL
* @author Maxence Bernard
*/
public final class Credentials {
private final String login;
private final String password;
/**
* Creates a new instance with the supplied login and password.
* Any provided null values will be replaced by empty strings.
*
* @param login the login part as a string
* @param password the password part as a string
*/
public Credentials(String login, String password) {
this.login = login == null ? "" : login;
this.password = password == null ? "" : password;
}
/**
* Returns the login part. The returned login may be an empty string but never
* Credentials are said to be empty if both login and password are empty strings.
*
* @return
* Empty Credentials and
* The {@link #getRealm(FileURL)} implementation returns a URL with the same scheme and host (if any) as the specified
* URL, and a path set to This parser can not only parse URLs but also local absolute paths and UNC paths. Upon parsing, these paths are
* turned into equivalent, fully qualified URLs.
*
*
* Local absolute paths are turned into corresponding 'file' URLs. Local paths are system-dependent, their form and
* path separator vary from one OS to the other. Only native paths are supported, i.e. Windows-style paths are supported
* only when running on Windows (or OS/2), Unix-style paths only on an OS that uses them natively...
* Here are a couple example of how local paths are parsed and turned into FileURL instances:
*
* Windows-style UNC paths such as This class should NOT be subclassed for proper AbstractFile implementations. It should only be used in certain
* circumstances that require creating a quick AbstractFile implementation where only a few methods will be used.
*
* @author Maxence Bernard
*/
public class DummyFile extends AbstractFile {
public DummyFile(FileURL url) {
super(url);
}
/**
* Implementation notes: always returns See the {@link MutableFileAttributes} for an extended interface that include file attribute setters.
*
* @see MutableFileAttributes
* @see SimpleFileAttributes
* @author Maxence Bernard
*/
public interface FileAttributes {
/**
* Returns the file's path, The format and separator character of the path are filesystem-dependent.
*
* @return the file's path,
* In order to allow the
* Built-in file protocols are:
*
* In order to allow the
* Built-in file formats are:
*
* If a {@link ProtocolProvider} was already registered to the specified protocol, it will automatically be
* unregistered.
*
*
* The
* After this call, the various {@link #getFile(String) getFile} methods will be able to resolve files using the
* specified protocol.
*
*
* Built-in file protocols are listed in {@link FileProtocols}.
*
* @param protocol identifier of the protocol to register.
* @param provider object used to create instances of files using the specified protocol.
* @return the previously registered protocol provider if any, All objects returned by the iterator's
* To unregister the provider of a particular archive format without knowing the associated provider instance, use
* {@link #getArchiveFormatProvider(String)} with a known archive filename to retrieve the provider instance.
* For example, This method does not throw any IOException but returns This method does not throw any IOException but returns Specifying the file parent if an instance already exists allows to recycle the AbstractFile instance
* instead of creating a new one when the parent file is requested.
*
* @param fileURL the file URL representing the file to be created
* @param authenticator used to authenticate the specified location if its protocol
* {@link FileURL#getAuthenticationType() is authenticated} and the location contains no credentials already.
* If the value is The returned file may be a {@link LocalFile} or a {@link AbstractArchiveFile} if the extension corresponds
* to a registered archive format.
*
* @param desiredFilename the desired filename for the temporary file. If a file with this name already exists
* in the temp directory, the filename's prefix (name without extension) will be appended an ID, but the filename's
* extension will always be preserved.
* @param deleteOnExit if
* Note that return {@link AbstractArchiveFile} instances may not actually be archives according to
* {@link AbstractFile#isArchive()}. An {@link AbstractArchiveFile} instance that is not currently an archive
* (either non-existent or a directory) will behave as a regular (non-archive) file. This allows file instances to
* go from being an archive to not being an archive (and vice-versa), without having to re-resolve the file.
*/
public static AbstractFile wrapArchive(AbstractFile file) throws IOException {
String filename = file.getName();
// Looks for an archive FilenameFilter that matches the given filename.
// Comparing the filename against each and every archive extension has a cost, so we only perform the test if
// the filename contains a dot '.' character, since most of the time this method is called with a filename that
// doesn't match any of the filters.
if (filename.indexOf('.') >= 0) {
ArchiveFormatProvider provider = getArchiveFormatProvider(filename);
if (provider != null) {
return provider.getFile(file);
}
}
return file;
}
/**
* Same as wrapArchive(AbstractFile) but using the given extension rather than the file's extension.
*/
public static AbstractFile wrapArchive(AbstractFile file, String extension) throws IOException {
ArchiveFormatProvider provider = getArchiveFormatProvider(file.getBaseName() + extension);
return provider != null ? provider.getFile(file) : file;
}
/**
* Returns the default {@link com.mucommander.commons.file.icon.FileIconProvider} instance. The default provider class
* (before {@link #setDefaultFileIconProvider(com.mucommander.commons.file.icon.FileIconProvider)} is called) is
* platform-dependent and as such may vary across platforms.
*
* It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}
* to create and return the icon.
*
* @return the default FileIconProvider implementation
*/
public static FileIconProvider getDefaultFileIconProvider() {
return defaultFileIconProvider;
}
/**
* Sets the default {@link com.mucommander.commons.file.icon.FileIconProvider} implementation.
*
* It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}
* to create and return the icon.
*
* @param fip the new value for the default FileIconProvider
*/
public static void setDefaultFileIconProvider(FileIconProvider fip) {
defaultFileIconProvider = fip;
}
/**
* Returns the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file.
*
* @return the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file
* @see Authenticator
*/
public static Authenticator getDefaultAuthenticator() {
return defaultAuthenticator;
}
/**
* Sets the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file.
*
* @param authenticator the new default {@link Authenticator}
* @see Authenticator
*/
public static void setDefaultAuthenticator(Authenticator authenticator) {
defaultAuthenticator = authenticator;
}
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(FileFactory.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FileOperation.java
================================================
package com.mucommander.commons.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
/**
* @author Maxence Bernard
* @see UnsupportedFileOperationException
* @see AbstractFile
*/
public enum FileOperation {
/**
* Represents a 'read' operation, as specified by {@link AbstractFile#getInputStream()}.
*
* @see AbstractFile#getInputStream()
**/
READ_FILE,
/**
* Represents a 'random read' operation, as specified by {@link AbstractFile#getRandomAccessInputStream()}.
*
* @see AbstractFile#getRandomAccessInputStream()
**/
RANDOM_READ_FILE,
/**
* Represents a 'write' operation, as specified by {@link AbstractFile#getOutputStream()}.
*
* @see AbstractFile#getOutputStream()
**/
WRITE_FILE,
/**
* Represents an 'append' operation, as specified by {@link AbstractFile#getAppendOutputStream()}.
*
* @see AbstractFile#getAppendOutputStream()
**/
APPEND_FILE,
/**
* Represents a 'random write' operation, as specified by {@link AbstractFile#getRandomAccessOutputStream()}.
*
* @see AbstractFile#getRandomAccessOutputStream()
**/
RANDOM_WRITE_FILE,
/**
* Represents an 'mkdir' operation, as specified by {@link AbstractFile#mkdir()}.
*
* @see AbstractFile#mkdir()
**/
CREATE_DIRECTORY,
/**
* Represents an 'ls' operation, as specified by {@link AbstractFile#ls()}.
*
* @see AbstractFile#ls()
**/
LIST_CHILDREN,
/**
* Represents a 'delete' operation, as specified by {@link AbstractFile#delete()}.
*
* @see AbstractFile#delete()
**/
DELETE,
/**
* Represents a 'remove copy' operation, as specified by {@link AbstractFile#copyRemotelyTo(AbstractFile)}.
*/
COPY_REMOTELY,
/**
* Represents a 'rename' operation, as specified by {@link AbstractFile#renameTo(AbstractFile)}.
*/
RENAME,
/**
* Represents a 'change date' operation, as specified by {@link AbstractFile#setLastModifiedDate(long)}.
*
* @see AbstractFile#setLastModifiedDate(long)
**/
CHANGE_DATE,
/**
* Represents a 'change permission' operation, as specified by {@link AbstractFile#changePermission(int, int, boolean)}.
*/
CHANGE_PERMISSION,
/**
* Represents a 'get free space' operation, as specified by {@link AbstractFile#getFreeSpace()}.
*/
GET_FREE_SPACE,
/**
* Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.
*/
GET_BLOCKSIZE,
/**
* Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.
*/
GET_REPLICATION,
/**
* Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.
*/
CHANGE_REPLICATION,
/**
* Represents a 'get total space' operation, as specified by {@link AbstractFile#getTotalSpace()}.
*/
GET_TOTAL_SPACE;
private static final Logger LOGGER = LoggerFactory.getLogger(FileOperation.class);
/**
* Returns the {@link AbstractFile} method corresponding to this file operation.
*
* @param c the AbstractFile class for which to return a This interface also defines constants for commonly used file permissions.
*
* @see com.mucommander.commons.file.AbstractFile#getPermissions()
* @author Maxence Bernard
*/
public interface FilePermissions extends PermissionBits {
/** Empty file permissions: read/write/execute permissions cleared for user/group/other (0), none of the permission
* bits are supported (mask is 0) */
FilePermissions EMPTY_FILE_PERMISSIONS = new SimpleFilePermissions(0, 0);
/** Default file permissions used by {@link AbstractFile#importPermissions(AbstractFile)} for permission bits that
* are not available in the source: rw-r--r-- (644 octal). All the permission bits are marked as supported. */
FilePermissions DEFAULT_FILE_PERMISSIONS = new SimpleFilePermissions(420, FULL_PERMISSION_BITS);
/** Default directory permissions used by {@link AbstractFile#importPermissions(AbstractFile)} for permission bits that
* are not available in the source: rwxr-xr-x (755 octal). All the permission bits are marked as supported. */
FilePermissions DEFAULT_DIRECTORY_PERMISSIONS = new SimpleFilePermissions(493, FULL_PERMISSION_BITS);
FilePermissions DEFAULT_EXECUTABLE_PERMISSIONS = new SimpleFilePermissions(493, FULL_PERMISSION_BITS);
/**
* Returns the mask that indicates which permission bits are significant and should be taken into account.
* Permission bits that are unsupported have no meaning and their value should simply be ignored.
*
* @return the mask that indicates which permission bits are significant and should be taken into account.
*/
PermissionBits getMask();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FileProtocols.java
================================================
package com.mucommander.commons.file;
/**
* This interface contains a set of known protocol names, that can be found in {@link FileURL}.
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public interface FileProtocols {
/** Protocol for local or locally mounted files. */
String FILE = "file";
/** Protocol for files served by an FTP server. */
String FTP = "ftp";
/** Protocol for files served by a web server using HTTP. */
String HTTP = "http";
/** Protocol for files served by an HDFS (Hadoop distributed filesystem) cluster. */
String HDFS = "hdfs";
/** Protocol for files served by a web server using HTTPS. */
String HTTPS = "https";
/** Protocol for files served by an NFS server. */
String NFS = "nfs";
/** Protocol for files served by an Amazon S3 (or protocol-compatible) server. */
String S3 = "s3";
/** Protocol for files served by an SFTP server (not to be confused with FTPS or SCP). */
String SFTP = "sftp";
/** Protocol for files served by an SMB/CIFS server. */
String SMB = "smb";
/** Protocol for files served by a web server using WebDAV/HTTP. */
String WEBDAV = "webdav";
/** Protocol for files served by a web server using WebDAV/HTTPS. */
String WEBDAVS = "webdavs";
/** Protocol for files served by a web server using vSphere. */
String VSPHERE = "vsphere";
/** Protocol for files on android devices. */
String ADB = "adb";
/** Protocol for avrdude programmer. */
String AVR = "avr";
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FileURL.java
================================================
package com.mucommander.commons.file;
import com.mucommander.commons.file.compat.CompatURLStreamHandler;
import com.mucommander.commons.file.impl.local.LocalFile;
import com.mucommander.commons.file.util.PathUtils;
import com.mucommander.commons.runtime.OsFamily;
import com.mucommander.commons.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This class represents a Uniform Resource Locator (URL). The general format of a URL is as follows:
*
* FileURL cannot be instantiated directly, instances can be created using {@link #getFileURL(String)}.
* Unlike the
* In addition to standard URL features, FileURL gives access to scheme-specific attributes:
*
* In addition to providing those attributes, a SchemeHandler provides a {@link SchemeParser}
* instance which takes care of the actual parsing of URLs of a particular scheme when {@link #getFileURL(String)} is
* invoked. This allows for scheme-specific parsing, like for example for the query part which should only be parsed
* and considered as a separate part for certain schemes such as HTTP.
*
* This class registers a number of handlers for the schemes/protocols supported by the muCommander file API.
* Additional handlers can be registered dynamically using {@link #registerHandler(String, SchemeHandler)}. Likewise,
* existing handlers can be unregistered or replaced at runtime using
* A {@link #getDefaultHandler() default handler} is used for schemes that do not have a specific handler registered.
* It provides default values for the above-mentioned attributes and provides a parser that parses those scheme URLs.
* The default handler's parser is also used for parsing locations passed to {@link #getFileURL(String)} that do not
* contain a scheme (i.e. without the leading
* This class provides methods to attach properties to a FileURL instance. These properties are not part of the URL
* itself and are absent from its string representation. They allow protocol-specific properties like connection
* settings to be passed along, to {@link AbstractFile} instances in particular.
*
*
* This class has the several limitations that are worth noting:
*
* Important: after calling this method, the scheme should also be changed to match the new handler --
* changing the handler without changing the scheme to an appropriate one will result in inconsistent
* scheme-specific attributes to be returned.
*
* @param handler the
* Important: after calling this method, the handler should also be changed to match the new scheme --
* changing the scheme without changing the handler to an appropriate one will result in inconsistent
* scheme-specific attributes to be returned.
*
* @param scheme new scheme part of this URL.
* @throws IllegalArgumentException if the specified is null or an empty string
* @see #getScheme()
*/
public void setScheme(String scheme) {
if (scheme == null) {
throw new IllegalArgumentException();
}
this.scheme = scheme;
urlModified();
}
/**
* Returns the host part of this URL, Some file protocols may not have a notion of standard port or even no use for the port part at all, for
* example those that are not TCP or UDP based such as the local 'file' scheme.
*
* This method is just a shorthand for This method is just a shorthand for The returned credentials may or may be of any use for the scheme's file protocol depending on the value
* returned by {@link #getAuthenticationType()}.
*
* @return the credentials contained by this URL, Credentials may or may not be of any use for the scheme's file protocol depending on the value
* returned by {@link #getAuthenticationType()}.
*
* @param credentials the new login and password parts, replacing any existing credentials. If null is passed,
* existing credentials will be discarded.
* @see #getCredentials()
*/
public void setCredentials(Credentials credentials) {
// Empty credentials are equivalent to null credentials
this.credentials = credentials == null || credentials.isEmpty() ? null : credentials;
urlModified();
}
/**
* Returns this scheme's guest credentials,
* Guest credentials offer a way to authenticate a URL as a 'guest' on file protocols that require a set of
* credentials to establish a connection. The returned credentials are provided with no guarantee that the filesystem
* will actually accept them and allow the request/connection. The notion of 'guest' credentials may or may not
* have a meaning depending on the underlying file protocol.
*
* This method is just a shorthand for This method is just a shorthand for
* The returned FileURL will have the same handler, scheme, host, port, credentials and properties as this one.
* The query part of the returned parent URL will always be Note: this method returns a new FileURL instance every time it is called, and all mutable fields of this FileURL
* are cloned. Therefore, the returned URL can be safely modified without any risk of side effects.
*
* @return this URL's parent, Note: this method returns a new FileURL instance every time it is called. Therefore the returned FileURL can
* safely be modified without any risk of side effects.
* This method is just a shorthand for
* There is no The returned It is important to note that this method is provided for interoperability purposes, for the sole purpose of
* connecting to APIs that require a It is noteworthy that this method uses
* Two
* Credentials (login and password parts) are compared only if requested. The comparison for both the login and
* password is case-sensitive. See the {@link SimpleFileAttributes} class for an implementation of this interface.
*
* @author Maxence Bernard
* @see SimpleFileAttributes
*/
public interface MutableFileAttributes extends FileAttributes {
/**
* Sets the file's path.
*
* The format and separator character of the path are filesystem-dependent.
*
* @param path the file's path
*/
void setPath(String path);
/**
* Sets whether the file exists physically on the underlying filesystem.
*
* @param exists Permission bits can be queried individually using {@link #getBitValue(int, int)} or be represented altogether
* as a UNIX-style permission int, using {@link #getIntValue()}.
*
* Two implementations of this interface are provided:
*
* For {@link AbstractFile} implementations to be automatically instantiated by {@link FileFactory}, this interface
* needs to be implemented and an instance registered with {@link FileFactory} and bound to a protocol identifier.
*
* @author Nicolas Rinaudo, Maxence Bernard
* @see FileFactory
* @see AbstractFile
*/
public interface ProtocolProvider {
/**
* Creates a new instance of
* Throws a {@link UnsupportedFileOperationException} if the underlying file does not support the required
* read and write {@link FileOperation file operations}. Throws an
* Throws a {@link UnsupportedFileOperationException} if the underlying file does not support the required
* read and write {@link FileOperation file operations}. Throws an
* This method will create this entry as a regular file in the archive if it doesn't already exist, or replace
* it if it already does.
*
* @throws IOException if an I/O error occurred
*/
@Override
public OutputStream getOutputStream() throws IOException {
if (entry.exists()) {
try {
delete();
} catch(IOException e) {
// Go ahead and try to add the file anyway
}
}
// Update the ArchiveEntry's size as data gets written to the OutputStream
OutputStream out = new CounterOutputStream(((AbstractRWArchiveFile)archiveFile).addEntry(entry),
new ByteCounter() {
@Override
public synchronized void add(long nbBytes) {
entry.setSize(entry.getSize()+nbBytes);
entry.setDate(System.currentTimeMillis());
}
});
entry.setExists(true);
return out;
}
@Override
public void changePermissions(int permissions) throws IOException {
if (!entry.exists()) {
throw new IOException();
}
FilePermissions oldPermissions = entry.getPermissions();
FilePermissions newPermissions = new SimpleFilePermissions(permissions, oldPermissions.getMask());
entry.setPermissions(newPermissions);
boolean success = updateEntryAttributes();
if (!success) { // restore old permissions if attributes could not be updated
entry.setPermissions(oldPermissions);
}
if (!success) {
throw new IOException();
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/SchemeHandler.java
================================================
package com.mucommander.commons.file;
/**
*
* In addition to providing those attributes, a SchemeHandler provides a {@link SchemeParser} instance which takes care
* of the actual parsing of URLs of a particular scheme when {@link FileURL#getFileURL(String)} is invoked. This allows
* for scheme-specific parsing, like for example for the query part which should only be parsed and considered as a
* separate part for certain schemes such as HTTP.
*
*
* This method returns a new FileURL instance every time it is called. Therefore, the returned URL can
* safely be modified without any risk of side effects.
*
* @param location the location for which to return the authentication realm
* @return the authentication realm of the specified url
*/
FileURL getRealm(FileURL location);
/**
* Returns the scheme's guest credentials,
* Guest credentials offer a way to authenticate a URL as a 'guest' on file protocols that require a set of
* credentials to establish a connection. The returned credentials are provided with no guarantee that the filesystem
* will actually accept them and allow the request/connection. The notion of 'guest' credentials may or may not
* have a meaning depending on the underlying file protocol.
*
* @return the scheme's guest credentials, Some parts such as the query and fragment have a meaning only for certain schemes such as HTTP, other schemes
* may simply ignore the corresponding query/fragment delimiters ('?' and '#' resp.) and include them in the
* path part.
*
* @param url the URL to parse
* @param fileURL the FileURL instance in which to set the different parsed parts
* @throws MalformedURLException if the specified string is not a valid URL and cannot be parsed
*/
void parse(String url, FileURL fileURL) throws MalformedURLException;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/SevenZipArchiveFormatDetector.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2014-2026 Oleg Trifonov
*
* trolCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* trolCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see Attributes can also be manually updated using attribute setters. The {@link #updateExpirationDate()} method allows
* to reset the expiration date and consider the attributes as 'fresh'.
*
* An initial value for the attributes 'time to live' is specified in the constructor and can later be changed using
* {@link #setTtl(long)}. If the 'time to live' is set to -1, attributes are no longer automatically updated, this class
* then simply acts as {@link com.mucommander.commons.file.SimpleFileAttributes}.
*
* @author Maxence Bernard
*/
public abstract class SyncedFileAttributes extends SimpleFileAttributes {
/** The attributes' 'time to live', negative values disable automatic attributes updates */
private long ttl;
/** The attributes' expiration timestamp/date */
private long expirationDate;
/** True when attributes are being updated */
private boolean isUpdating;
/**
* Creates a new SyncedFileAttributes using the specifies 'time to live' value.
*
* @param ttl the attributes' 'time to live', in milliseconds
* @param updateAttributesNow if
* This exception is to be thrown in a way that is independent of the actual file instance, and of I/O or
* network conditions: an Subclasses implement specific archive formats (Zip, Tar...) but cannot be instantiated directly.
* Instead, the Archive formats fall into 2 categories:
* Implementation note: Archiver implementations must override this method to handle comments
*
* @param comment the comment to be stored in the archive
*/
public void setComment(String comment) {
// No-op
}
/**
* Normalizes the entry path, that is :
* This method will first attempt to get a {@link RandomAccessOutputStream} if the given file is able to supply
* one, and if not, fall back to a regular If the entry is a regular file (not a directory), an OutputStream which can be used to write the contents
* of the entry will be returned, If this Archiver uses a single entry format, the specified path and file won't be used at all.
* Also in this case, this method must be invoked only once (single entry), it will throw an IOException
* if invoked more than once.
*
* @param entryPath the path to be used to create the entry in the archive. This parameter is simply ignored if the
* archive is a single entry format.
* @param attributes used to determine whether the entry is a directory or regular file, and to retrieve its
* date and size
* @return By default, this value is 300 seconds (5 minutes).
*
* @return the number of seconds of inactivity after which {@link ConnectionPool} will close the connection,
* By default, this value is 300 seconds (5 minutes).
*
* @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will close the connection,
* By default, this value is -1 (keep alive disabled).
*
* @return the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive
* by calling {@link #keepAlive()}, By default, this value is -1 (keep alive disabled).
*
* @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection
* alive by calling {@link #keepAlive()}, Implementation note: This method must not perform any I/O which could block the calling thread.
*
* @return Implementation note: the implementation must guarantee that any calls to {@link #isConnected()} after this
* method has been called return false.
*/
public abstract void closeConnection();
/**
* Keeps this connection alive.
*
* Implementation note: if keep alive is not available in the underlying protocol or
* simply unnecessary, this method should be implemented as a no-op (do nothing).
*/
public abstract void keepAlive();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/connection/ConnectionHandlerFactory.java
================================================
package com.mucommander.commons.file.connection;
import com.mucommander.commons.file.FileURL;
/**
* This interface should be implemented by classes that are able to create ConnectionHandler instances for a given
* server location, typically {@link com.mucommander.commons.file.AbstractFile} implementations.
*
* This interface allows to take advantage of {@link ConnectionPool} to share connections across
* {@link com.mucommander.commons.file.AbstractFile} instances.
*
* @author Maxence Bernard
*/
public interface ConnectionHandlerFactory {
/**
* Creates and returns a {@link ConnectionHandler} instance for the given location.
*/
ConnectionHandler createConnectionHandler(FileURL location);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/connection/ConnectionPool.java
================================================
package com.mucommander.commons.file.connection;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mucommander.commons.file.Credentials;
import com.mucommander.commons.file.FileURL;
/**
* @see com.mucommander.commons.file.connection.ConnectionHandler
* @author Maxence Bernard
*/
public class ConnectionPool implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPool.class);
/** Singleton instance */
private static final ConnectionPool instance = new ConnectionPool();
/** List of registered ConnectionHandler */
private final static List The {@link #isInverted() inverted} mode has no effect on the values returned by this method.
*
* @param value the value to be tested
* @return true if the given value was rejected by this filter
*/
public boolean reject(C value) {
return !accept(value);
}
/**
* Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and
* returns a file array of matched The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)
* the '.' character must be included in the specified extension (e.g. ".zip" must be used, not just "zip").
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class AbstractExtensionFilter extends AbstractStringCriterionFilter {
/** File extensions to match against criterion values */
private final char[][] extensions;
/**
* Creates a new If this {@link ChainedFileFilter} contains no filter, this method will always return Only one attribute can be matched at a time. To match several attributes, combine them using a
* {@link com.mucommander.commons.file.filter.ChainedFileFilter}.
*
* @author Maxence Bernard
*/
public class AttributeFileFilter extends AbstractFileFilter {
public enum FileAttribute {
/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isDirectory() directory}. */
DIRECTORY,
/** Tests if the file is a regular file, i.e. not a directory. This is equivalent to negating {@link #DIRECTORY}. */
FILE,
/** Tests if the file is {@link com.mucommander.commons.file.AbstractFile#isBrowsable() browsable}. */
BROWSABLE,
/** Tests if the file is an {@link com.mucommander.commons.file.AbstractFile#isArchive() archive}. */
ARCHIVE,
/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isSymlink() symlink}. */
SYMLINK,
/** Tests if the file is {@link com.mucommander.commons.file.AbstractFile#isHidden() hidden}. */
HIDDEN,
/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isRoot() root folder}. */
ROOT,
/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isSystem() system file}. */
SYSTEM
}
/** The attribute to test files against */
private FileAttribute attribute;
/**
* Creates a new The {@link AndFileFilter} and {@link OrFileFilter} implementations match files that respectively match all of
* the registered filters, or any of them.
*
* @see AndFileFilter
* @see OrFileFilter
* @author Maxence Bernard
*/
public abstract class ChainedFileFilter extends AbstractFileFilter {
/** List of registered FileFilter */
protected List Several convenience methods are provided to operate this filter on a set of criteria values, and filter out
* values that are rejected by this filter.
*
* @see AbstractPathFilter
* @author Maxence Bernard
*/
public interface CriterionFilter The {@link #isInverted() inverted} mode has no effect on the values returned by this method.
*
* @param value the value to be tested
* @return true if the given value was rejected by this filter
*/
boolean reject(C value);
/**
* Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and
* returns a file array of matched The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)
* the '.' character must be included in the specified extension (e.g. ".zip" must be used, not just "zip").
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class ExtensionFilenameFilter extends AbstractExtensionFilter implements FilenameFilter {
/**
* Creates a case-insensitive The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)
* the '.' character must be included in the specified extension (e.g. ".zip" must be used, not just "zip").
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class ExtensionPathFilter extends AbstractExtensionFilter implements PathFilter {
/**
* Creates a case-insensitive Several convenience methods are provided to operate this filter on a set of files, and filter out files that
* do not match this filter.
*
* A The {@link #isInverted() inverted} mode has no effect on the values returned by this method.
*
* @param file the file to test
* @return true if the given file was rejected by this FileFilter
*/
boolean reject(AbstractFile file);
/**
* Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and
* returns a file array of matched The {@link #isInverted() inverted} mode has no effect on the values returned by this method.
*
* @param file the file to test
* @return true if the given file was accepted by this FileFilter
*/
boolean accept(AbstractFile file);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/FileOperationFilter.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileOperation;
/**
* Only one file operation can be matched at a time. To match several file operations, combine them using a
* {@link com.mucommander.commons.file.filter.ChainedFileFilter}.
*
* @see FileOperation
* @author Maxence Bernard
*/
public class FileOperationFilter extends AbstractFileFilter {
/** The file operation to match */
private FileOperation op;
public FileOperationFilter(FileOperation op) {
this(op, false);
}
public FileOperationFilter(FileOperation op, boolean inverted) {
super(inverted);
setFileOperation(op);
}
/**
* Returns the file operation this filter matches.
*
* @return the file operation this filter matches.
*/
public FileOperation getFileOperation() {
return op;
}
/**
* Sets the file operation this filter matches, replacing the previous operation.
*
* @param op the file operation this filter matches.
*/
public void setFileOperation(FileOperation op) {
this.op = op;
}
@Override
public boolean accept(AbstractFile file) {
return file.isFileOperationSupported(op);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/FilenameFilter.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* A If this {@link ChainedFileFilter} contains no filter, this method will always return This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* each time an icon is requested. If This method is called only if the prior call to {@link #isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* returned This method is called only if the prior call to {@link #isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* returned This class does not actually provide icons nor does it manage the contents of the cache ; it delegates these tasks
* to a {@link CacheableFileIconProvider} instance. All this class does is use the cache implementation to harness its
* benefits and take all the credit for it. If the file icon is cacheable, {@link CacheableFileIconProvider#lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* is called to look for a previously cached icon. If a value is found, it is returned. If not,
* {#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} is called on the If the file icon is not cacheable, {#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* is simply called on the The specified Icons are stored as {@link java.lang.ref.SoftReference soft references} so they can be garbage collected
* when the VM runs low on memory.
*
* The implementation uses the {@link ReferenceMap} class part of the CharsetDetector class.
*
*/
public static String[] getAllDetectableCharsets() {
String[] allCharsetNames = new String[ALL_CS_RECOGNIZERS.size()];
for (int i = 0; i < allCharsetNames.length; i++) {
allCharsetNames[i] = ALL_CS_RECOGNIZERS.get(i).recognizer.getName();
}
return allCharsetNames;
}
/**
* Test whether input filtering is enabled.
*
* @return true if input text will be filtered.
*
* @see #enableInputFilter
*
*/
public boolean inputFilterEnabled() {
return fStripTags;
}
/**
* Enable filtering of input text. If filtering is enabled,
* text within angle brackets ("<" and ">") will be removed
* before detection.
*
* @param filter true to enable input text filtering.
*
* @return The previous setting.
*
*/
public boolean enableInputFilter(boolean filter) {
boolean previous = fStripTags;
fStripTags = filter;
return previous;
}
/*
* MungeInput - after getting a set of raw input data to be analyzed, preprocess
* it by removing what appears to be html markup.
*/
private void MungeInput() {
boolean inMarkup = false;
int openTags = 0;
int badTags = 0;
//
// html / xml markup stripping.
// quick and dirty, not 100% accurate, but hopefully good enough, statistically.
// discard everything within < brackets >
// Count how many total '<' and illegal (nested) '<' occur, so we can make some
// guess whether the input was actually marked up at all.
int srci;
if (fStripTags) {
int dsti = 0;
for (srci = 0; srci < fRawLength && dsti < fInputBytes.length; srci++) {
byte b = fRawInput[srci];
if (b == (byte)'<') {
if (inMarkup) {
badTags++;
}
inMarkup = true;
openTags++;
}
if (!inMarkup) {
fInputBytes[dsti++] = b;
}
if (b == (byte)'>') {
inMarkup = false;
}
}
fInputLen = dsti;
}
// If it looks like this input wasn't marked up, or if it looks like it's
// essentially nothing but markup abandon the markup stripping.
// Detection will have to work on the unstripped input.
if (openTags < 5 || openTags/5 < badTags || (fInputLen < 100 && fRawLength>600)) {
int limit = fRawLength;
if (limit > kBufSize) {
limit = kBufSize;
}
for (srci=0; srcitrue to enable, or false to disable the
* charset encoding.
* @return A reference to this CharsetDetector.
* @throws IllegalArgumentException when the name of charset encoding is
* not supported.
* internal
* @deprecated This API is ICU internal only.
*/
public CharsetDetector setDetectableCharset(String encoding, boolean enabled) {
int modIdx = -1;
boolean isDefaultVal = false;
for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {
CSRecognizerInfo csrinfo = ALL_CS_RECOGNIZERS.get(i);
if (csrinfo.recognizer.getName().equals(encoding)) {
modIdx = i;
isDefaultVal = (csrinfo.isDefaultEnabled == enabled);
break;
}
}
if (modIdx < 0) {
// No matching encoding found
throw new IllegalArgumentException("Invalid encoding: " + "\"" + encoding + "\"");
}
if (fEnabledRecognizers == null && !isDefaultVal) {
// create an array storing the non default setting
fEnabledRecognizers = new boolean[ALL_CS_RECOGNIZERS.size()];
// Initialize the array with default info
for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {
fEnabledRecognizers[i] = ALL_CS_RECOGNIZERS.get(i).isDefaultEnabled;
}
}
if (fEnabledRecognizers != null) {
fEnabledRecognizers[modIdx] = enabled;
}
return this;
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetMatch.java
================================================
/*
*******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
/**
* This class represents a charset that has been identified by a CharsetDetector
* as a possible encoding for a set of input data. From an instance of this
* class, you can ask for a confidence level in the charset identification,
* or for Java Reader or String to access the original byte data in Unicode form.
* null if the language cannot be determined.
*
*/
public String getLanguage() {
return fLang;
}
/**
* Compare to other CharsetMatch objects.
* Comparison is based on the match confidence value, which
* allows CharsetDetector.detectAll() to order its results.
*
* @param other the CharsetMatch object to compare against.
* @return a negative integer, zero, or a positive integer as the
* confidence level of this CharsetMatch
* is less than, equal to, or greater than that of
* the argument.
* @throws ClassCastException if the argument is not a CharsetMatch.
*/
public int compareTo (@NotNull CharsetMatch other) {
if (this.fConfidence > other.fConfidence) {
return 1;
} else if (this.fConfidence < other.fConfidence) {
return -1;
}
return 0;
}
/*
* Constructor. Implementation internal
*/
CharsetMatch(CharsetDetector det, CharsetRecognizer rec, int conf) {
fConfidence = conf;
// The references to the original application input data must be copied out
// of the charset recognizer to here, in case the application resets the
// recognizer before using this CharsetMatch.
if (det.fInputStream == null) {
// We only want the existing input byte data if it came straight from the user,
// not if is just the head of a stream.
fRawInput = det.fRawInput;
fRawLength = det.fRawLength;
}
fInputStream = det.fInputStream;
fCharsetName = rec.getName();
fLang = rec.getLanguage();
}
/*
* Constructor. Implementation internal
*/
CharsetMatch(CharsetDetector det, int conf, String csName, String lang) {
fConfidence = conf;
// The references to the original application input data must be copied out of the charset recognizer to here,
// in case the application resets the recognizer before using this CharsetMatch.
if (det.fInputStream == null) {
// We only want the existing input byte data if it came straight from the user,
// not if is just the head of a stream.
fRawInput = det.fRawInput;
fRawLength = det.fRawLength;
}
fInputStream = det.fInputStream;
fCharsetName = csName;
fLang = lang;
}
private final int fConfidence;
private byte[] fRawInput; // Original, untouched input bytes. If user gave us a byte array, this is it.
private int fRawLength; // Length of data in fRawInput array.
private final InputStream fInputStream; // User's input stream, or null if the user gave us a byte array.
private final String fCharsetName; // The name of the charset this CharsetMatch represents. Filled in by the recognizer.
private final String fLang; // The language, if one was determined by the recognizer during the detect operation.
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecog_2022.java
================================================
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
* class CharsetRecog_2022 part of the ICU charset detection implementation.
* This is a superclass for the individual detectors for each of the detectable members of the ISO 2022 family of encodings.
* The separate classes are nested within this class.
*/
abstract class CharsetRecog_2022 extends CharsetRecognizer {
/**
* Matching function shared among the 2022 detectors JP, CN and KR Counts up the number of legal an
* unrecognized escape sequences in the sample of text, and computes a score based on the total number &
* the proportion that fit the encoding.
*
* @param text the byte buffer containing text to analyse
* @param textLen the size of the text in the byte.
* @param escapeSequences the byte escape sequences to test for.
* @return match quality, in the range of 0-100.
*/
int match(byte[] text, int textLen, byte[][] escapeSequences) {
int hits = 0;
int misses = 0;
int shifts = 0;
int quality;
scanInput:
for (int i = 0; i < textLen; i++) {
if (text[i] == 0x1b) {
checkEscapes:
for (byte[] seq : escapeSequences) {
if ((textLen - i) < seq.length) {
continue checkEscapes;
}
for (int j = 1; j < seq.length; j++) {
if (seq[j] != text[i + j]) {
continue checkEscapes;
}
}
hits++;
i += seq.length - 1;
continue scanInput;
}
misses++;
}
if (text[i] == 0x0e || text[i] == 0x0f) {
// Shift in/out
shifts++;
}
}
if (hits == 0) {
return 0;
}
// Initial quality is based on relative proportion of recognized vs.
// unrecognized escape sequences.
// All good: quality = 100;
// half or less good: quality = 0;
// linear inbetween.
quality = (100 * hits - 100 * misses) / (hits + misses);
// Back off quality if there were too few escape sequences seen.
// Include shifts in this computation, so that KR does not get penalized
// for having only a single Escape sequence, but many shifts.
if (hits + shifts < 5) {
quality -= (5 - (hits + shifts)) * 10;
}
if (quality < 0) {
quality = 0;
}
return quality;
}
static class CharsetRecog_2022JP extends CharsetRecog_2022 {
private final byte[][] escapeSequences = {
{0x1b, 0x24, 0x28, 0x43}, // KS X 1001:1992
{0x1b, 0x24, 0x28, 0x44}, // JIS X 212-1990
{0x1b, 0x24, 0x40}, // JIS C 6226-1978
{0x1b, 0x24, 0x41}, // GB 2312-80
{0x1b, 0x24, 0x42}, // JIS X 208-1983
{0x1b, 0x26, 0x40}, // JIS X 208 1990, 1997
{0x1b, 0x28, 0x42}, // ASCII
{0x1b, 0x28, 0x48}, // JIS-Roman
{0x1b, 0x28, 0x49}, // Half-width katakana
{0x1b, 0x28, 0x4a}, // JIS-Roman
{0x1b, 0x2e, 0x41}, // ISO 8859-1
{0x1b, 0x2e, 0x46} // ISO 8859-7
};
String getName() {
return "ISO-2022-JP";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_2022KR extends CharsetRecog_2022 {
private final byte[][] escapeSequences = {
{0x1b, 0x24, 0x29, 0x43}
};
String getName() {
return "ISO-2022-KR";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_2022CN extends CharsetRecog_2022 {
private final byte[][] escapeSequences = {
{0x1b, 0x24, 0x29, 0x41}, // GB 2312-80
{0x1b, 0x24, 0x29, 0x47}, // CNS 11643-1992 Plane 1
{0x1b, 0x24, 0x2A, 0x48}, // CNS 11643-1992 Plane 2
{0x1b, 0x24, 0x29, 0x45}, // ISO-IR-165
{0x1b, 0x24, 0x2B, 0x49}, // CNS 11643-1992 Plane 3
{0x1b, 0x24, 0x2B, 0x4A}, // CNS 11643-1992 Plane 4
{0x1b, 0x24, 0x2B, 0x4B}, // CNS 11643-1992 Plane 5
{0x1b, 0x24, 0x2B, 0x4C}, // CNS 11643-1992 Plane 6
{0x1b, 0x24, 0x2B, 0x4D}, // CNS 11643-1992 Plane 7
{0x1b, 0x4e}, // SS2
{0x1b, 0x4f}, // SS3
};
String getName() {
return "ISO-2022-CN";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecog_UTF8.java
================================================
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
* Charset recognizer for UTF-8
*/
class CharsetRecog_UTF8 extends CharsetRecognizer {
String getName() {
return "UTF-8";
}
/* (non-Javadoc)
* @see com.ibm.icu.text.CharsetRecognizer#match(com.ibm.icu.text.CharsetDetector)
*/
CharsetMatch match(CharsetDetector det) {
boolean hasBOM = false;
int numValid = 0;
int numInvalid = 0;
byte[] input = det.fRawInput;
int trailBytes;
if (det.fRawLength >= 3 &&
(input[0] & 0xFF) == 0xef && (input[1] & 0xFF) == 0xbb && (input[2] & 0xFF) == 0xbf) {
hasBOM = true;
}
// Scan for multibyte sequences
for (int i = 0; i < det.fRawLength; i++) {
int b = input[i];
if ((b & 0x80) == 0) {
continue; // ASCII
}
// High bit on char found. Figure out how long the sequence should be
if ((b & 0x0e0) == 0x0c0) {
trailBytes = 1;
} else if ((b & 0x0f0) == 0x0e0) {
trailBytes = 2;
} else if ((b & 0x0f8) == 0xf0) {
trailBytes = 3;
} else {
numInvalid++;
if (numInvalid > 5) {
break;
}
trailBytes = 0;
}
// Verify that we've got the right number of trail bytes in the sequence
for (; ; ) {
i++;
if (i >= det.fRawLength) {
break;
}
b = input[i];
if ((b & 0xc0) != 0x080) {
numInvalid++;
break;
}
if (--trailBytes == 0) {
numValid++;
break;
}
}
}
// Cook up some sort of confidence score, based on presense of a BOM and the existence of valid
// and/or invalid multibyte sequences.
int confidence = 0;
if (hasBOM && numInvalid == 0) {
confidence = 100;
} else if (hasBOM && numValid > numInvalid * 10) {
confidence = 80;
} else if (numValid > 3 && numInvalid == 0) {
confidence = 100;
} else if (numValid > 0 && numInvalid == 0) {
confidence = 80;
} else if (numValid == 0 && numInvalid == 0) {
// Plain ASCII.
confidence = 10;
} else if (numValid > numInvalid * 10) {
// Probably corrupt utf-8 data. Valid sequences aren't likely by chance.
confidence = 25;
}
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecog_Unicode.java
================================================
/*
*******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
*/
package com.ibm.icu.text;
/**
* This class matches UTF-16 and UTF-32, both big- and little-endian. The BOM will be used if it is present.
*/
abstract class CharsetRecog_Unicode extends CharsetRecognizer {
@Override
abstract String getName();
@Override
abstract CharsetMatch match(CharsetDetector det);
static class CharsetRecog_UTF_16_BE extends CharsetRecog_Unicode {
String getName() {
return "UTF-16BE";
}
CharsetMatch match(CharsetDetector det) {
byte[] input = det.fRawInput;
if (input.length >= 2 && ((input[0] & 0xFF) == 0xFE && (input[1] & 0xFF) == 0xFF)) {
int confidence = 100;
return new CharsetMatch(det, this, confidence);
}
// TODO: Do some statistics to check for unsigned UTF-16BE
return null;
}
}
static class CharsetRecog_UTF_16_LE extends CharsetRecog_Unicode {
String getName() {
return "UTF-16LE";
}
CharsetMatch match(CharsetDetector det) {
byte[] input = det.fRawInput;
if (input.length >= 2 && ((input[0] & 0xFF) == 0xFF && (input[1] & 0xFF) == 0xFE)) {
// An LE BOM is present.
if (input.length >= 4 && input[2] == 0x00 && input[3] == 0x00) {
// It is probably UTF-32 LE, not UTF-16
return null;
}
int confidence = 100;
return new CharsetMatch(det, this, confidence);
}
// TODO: Do some statistics to check for unsigned UTF-16LE
return null;
}
}
static abstract class CharsetRecog_UTF_32 extends CharsetRecog_Unicode {
abstract int getChar(byte[] input, int index);
abstract String getName();
CharsetMatch match(CharsetDetector det) {
byte[] input = det.fRawInput;
int limit = (det.fRawLength / 4) * 4;
int numValid = 0;
int numInvalid = 0;
boolean hasBOM = false;
int confidence = 0;
if (limit == 0) {
return null;
}
if (getChar(input, 0) == 0x0000FEFF) {
hasBOM = true;
}
for (int i = 0; i < limit; i += 4) {
int ch = getChar(input, i);
if (ch < 0 || ch >= 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF)) {
numInvalid += 1;
} else {
numValid += 1;
}
}
// Cook up some sort of confidence score, based on presence of a BOM
// and the existence of valid and/or invalid multibyte sequences.
if (hasBOM && numInvalid == 0) {
confidence = 100;
} else if (hasBOM && numValid > numInvalid * 10) {
confidence = 80;
} else if (numValid > 3 && numInvalid == 0) {
confidence = 100;
} else if (numValid > 0 && numInvalid == 0) {
confidence = 80;
} else if (numValid > numInvalid * 10) {
// Probably corrupt UTF-32BE data. Valid sequences aren't likely by chance.
confidence = 25;
}
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_UTF_32_BE extends CharsetRecog_UTF_32 {
int getChar(byte[] input, int index) {
return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
(input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);
}
String getName() {
return "UTF-32BE";
}
}
static class CharsetRecog_UTF_32_LE extends CharsetRecog_UTF_32 {
int getChar(byte[] input, int index) {
return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |
(input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);
}
String getName() {
return "UTF-32LE";
}
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecog_mbcs.java
================================================
/*
****************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
****************************************************************************
*
*/
package com.ibm.icu.text;
import java.util.Arrays;
/**
* CharsetRecognizer implemenation for Asian - double or multi-byte - charsets.
* Match is determined mostly by the input data adhering to the
* encoding scheme for the charset, and, optionally,
* frequency-of-occurence of characters.
*
* bits 0-7: the match confidence, ranging from 0-100
*
* bits 8-15: The match reason, an enum-like value.
*/
int match(CharsetDetector det, int [] commonChars) {
@SuppressWarnings("unused")
int singleByteCharCount = 0; //TODO Do we really need this?
int doubleByteCharCount = 0;
int commonCharCount = 0;
int badCharCount = 0;
int totalCharCount = 0;
int confidence = 0;
iteratedChar iter = new iteratedChar();
detectBlock: {
for (iter.reset(); nextChar(iter, det);) {
totalCharCount++;
if (iter.error) {
badCharCount++;
} else {
long cv = iter.charValue & 0xFFFFFFFFL;
if (cv <= 0xff) {
singleByteCharCount++;
} else {
doubleByteCharCount++;
if (commonChars != null) {
// NOTE: This assumes that there are no 4-byte common chars.
if (Arrays.binarySearch(commonChars, (int) cv) >= 0) {
commonCharCount++;
}
}
}
}
if (badCharCount >= 2 && badCharCount*5 >= doubleByteCharCount) {
// Bail out early if the byte data is not matching the encoding scheme.
break detectBlock;
}
}
if (doubleByteCharCount <= 10 && badCharCount== 0) {
// Not many multi-byte chars.
if (doubleByteCharCount == 0 && totalCharCount < 10) {
// There weren't any multibyte sequences, and there was a low density of non-ASCII single bytes.
// We don't have enough data to have any confidence.
// Statistical analysis of single byte non-ASCII charcters would probably help here.
confidence = 0;
}
else {
// ASCII or ISO file? It's probably not our encoding,
// but is not incompatible with our encoding, so don't give it a zero.
confidence = 10;
}
break detectBlock;
}
//
// No match if there are too many characters that don't fit the encoding scheme.
// (should we have zero tolerance for these?)
//
if (doubleByteCharCount < 20*badCharCount) {
confidence = 0;
break detectBlock;
}
if (commonChars == null) {
// We have no statistics on frequently occuring characters.
// Assess confidence purely on having a reasonable number of
// multi-byte characters (the more the better
confidence = 30 + doubleByteCharCount - 20*badCharCount;
if (confidence > 100) {
confidence = 100;
}
}else {
//
// Frequency of occurence statistics exist.
//
double maxVal = Math.log((float)doubleByteCharCount / 4);
double scaleFactor = 90.0 / maxVal;
confidence = (int)(Math.log(commonCharCount+1) * scaleFactor + 10);
confidence = Math.min(confidence, 100);
}
} // end of detectBlock:
return confidence;
}
// "Character" iterated character class.
// Recognizers for specific mbcs encodings make their "characters" available
// by providing a nextChar() function that fills in an instance of iteratedChar
// with the next char from the input.
// The returned characters are not converted to Unicode, but remain as the raw
// bytes (concatenated into an int) from the codepage data.
//
// For Asian charsets, use the raw input rather than the input that has been
// stripped of markup. Detection only considers multi-byte chars, effectively
// stripping markup anyway, and double byte chars do occur in markup too.
//
static class iteratedChar {
int charValue = 0; // 1-4 bytes from the raw input data
int index = 0;
int nextIndex = 0;
boolean error = false;
boolean done = false;
void reset() {
charValue = 0;
index = -1;
nextIndex = 0;
error = false;
done = false;
}
int nextByte(CharsetDetector det) {
if (nextIndex >= det.fRawLength) {
done = true;
return -1;
}
return (int)det.fRawInput[nextIndex++] & 0x00ff;
}
}
/**
* Get the next character (however many bytes it is) from the input data
* Subclasses for specific charset encodings must implement this function
* to get characters according to the rules of their encoding scheme.
* This function is not a method of class iteratedChar only because
* that would require a lot of extra derived classes, which is awkward.
* @param it The iteratedChar "struct" into which the returned char is placed.
* @param det The charset detector, which is needed to get at the input byte data
* being iterated over.
* @return True if a character was returned, false at end of input.
*/
abstract boolean nextChar(iteratedChar it, CharsetDetector det);
/**
* Shift-JIS charset recognizer.
*
*/
static class CharsetRecog_sjis extends CharsetRecog_mbcs {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
// of-occurence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0x8140, 0x8141, 0x8142, 0x8145, 0x815b, 0x8169, 0x816a, 0x8175, 0x8176, 0x82a0,
0x82a2, 0x82a4, 0x82a9, 0x82aa, 0x82ab, 0x82ad, 0x82af, 0x82b1, 0x82b3, 0x82b5,
0x82b7, 0x82bd, 0x82be, 0x82c1, 0x82c4, 0x82c5, 0x82c6, 0x82c8, 0x82c9, 0x82cc,
0x82cd, 0x82dc, 0x82e0, 0x82e7, 0x82e8, 0x82e9, 0x82ea, 0x82f0, 0x82f1, 0x8341,
0x8343, 0x834e, 0x834f, 0x8358, 0x835e, 0x8362, 0x8367, 0x8375, 0x8376, 0x8389,
0x838a, 0x838b, 0x838d, 0x8393, 0x8e96, 0x93fa, 0x95aa};
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
firstByte = it.charValue = it.nextByte(det);
if (firstByte < 0) {
return false;
}
if (firstByte <= 0x7f || (firstByte>0xa0 && firstByte<=0xdf)) {
return true;
}
int secondByte = it.nextByte(det);
if (secondByte < 0) {
return false;
}
it.charValue = (firstByte << 8) | secondByte;
if (! ((secondByte>=0x40 && secondByte<=0x7f) || (secondByte>=0x80 && secondByte<=0xff))) {
// Illegal second byte value.
it.error = true;
}
return true;
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det, commonChars);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
String getName() {
return "Shift_JIS";
}
public String getLanguage()
{
return "ja";
}
}
/**
* Big5 charset recognizer.
*
*/
static class CharsetRecog_big5 extends CharsetRecog_mbcs {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
// of-occurence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa140, 0xa141, 0xa142, 0xa143, 0xa147, 0xa149, 0xa175, 0xa176, 0xa440, 0xa446,
0xa447, 0xa448, 0xa451, 0xa454, 0xa457, 0xa464, 0xa46a, 0xa46c, 0xa477, 0xa4a3,
0xa4a4, 0xa4a7, 0xa4c1, 0xa4ce, 0xa4d1, 0xa4df, 0xa4e8, 0xa4fd, 0xa540, 0xa548,
0xa558, 0xa569, 0xa5cd, 0xa5e7, 0xa657, 0xa661, 0xa662, 0xa668, 0xa670, 0xa6a8,
0xa6b3, 0xa6b9, 0xa6d3, 0xa6db, 0xa6e6, 0xa6f2, 0xa740, 0xa751, 0xa759, 0xa7da,
0xa8a3, 0xa8a5, 0xa8ad, 0xa8d1, 0xa8d3, 0xa8e4, 0xa8fc, 0xa9c0, 0xa9d2, 0xa9f3,
0xaa6b, 0xaaba, 0xaabe, 0xaacc, 0xaafc, 0xac47, 0xac4f, 0xacb0, 0xacd2, 0xad59,
0xaec9, 0xafe0, 0xb0ea, 0xb16f, 0xb2b3, 0xb2c4, 0xb36f, 0xb44c, 0xb44e, 0xb54c,
0xb5a5, 0xb5bd, 0xb5d0, 0xb5d8, 0xb671, 0xb7ed, 0xb867, 0xb944, 0xbad8, 0xbb44,
0xbba1, 0xbdd1, 0xc2c4, 0xc3b9, 0xc440, 0xc45f};
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
firstByte = it.charValue = it.nextByte(det);
if (firstByte < 0) {
return false;
}
if (firstByte <= 0x7f || firstByte==0xff) {
// single byte character.
return true;
}
int secondByte = it.nextByte(det);
if (secondByte < 0) {
return false;
}
it.charValue = (it.charValue << 8) | secondByte;
if (secondByte < 0x40 ||
secondByte ==0x7f ||
secondByte == 0xff) {
it.error = true;
}
return true;
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det, commonChars);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
String getName() {
return "Big5";
}
public String getLanguage()
{
return "zh";
}
}
/**
* EUC charset recognizers. One abstract class that provides the common function
* for getting the next character according to the EUC encoding scheme,
* and nested derived classes for EUC_KR, EUC_JP, EUC_CN.
*
*/
abstract static class CharsetRecog_euc extends CharsetRecog_mbcs {
/*
* (non-Javadoc)
* Get the next character value for EUC based encodings.
* Character "value" is simply the raw bytes that make up the character
* packed into an int.
*/
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
int secondByte;
int thirdByte;
buildChar: {
firstByte = it.charValue = it.nextByte(det);
if (firstByte < 0) {
// Ran off the end of the input data
it.done = true;
break buildChar;
}
if (firstByte <= 0x8d) {
// single byte char
break buildChar;
}
secondByte = it.nextByte(det);
it.charValue = (it.charValue << 8) | secondByte;
if (firstByte >= 0xA1 && firstByte <= 0xfe) {
// Two byte Char
if (secondByte < 0xa1) {
it.error = true;
}
break buildChar;
}
if (firstByte == 0x8e) {
// Code Set 2.
// In EUC-JP, total char size is 2 bytes, only one byte of actual char value.
// In EUC-TW, total char size is 4 bytes, three bytes contribute to char value.
// We don't know which we've got.
// Treat it like EUC-JP. If the data really was EUC-TW, the following two
// bytes will look like a well formed 2 byte char.
if (secondByte < 0xa1) {
it.error = true;
}
break buildChar;
}
if (firstByte == 0x8f) {
// Code set 3.
// Three byte total char size, two bytes of actual char value.
thirdByte = it.nextByte(det);
it.charValue = (it.charValue << 8) | thirdByte;
if (thirdByte < 0xa1) {
it.error = true;
}
}
}
return !it.done;
}
/**
* The charset recognize for EUC-JP. A singleton instance of this class
* is created and kept by the public CharsetDetector class
*/
static class CharsetRecog_euc_jp extends CharsetRecog_euc {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
// of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa1a1, 0xa1a2, 0xa1a3, 0xa1a6, 0xa1bc, 0xa1ca, 0xa1cb, 0xa1d6, 0xa1d7, 0xa4a2,
0xa4a4, 0xa4a6, 0xa4a8, 0xa4aa, 0xa4ab, 0xa4ac, 0xa4ad, 0xa4af, 0xa4b1, 0xa4b3,
0xa4b5, 0xa4b7, 0xa4b9, 0xa4bb, 0xa4bd, 0xa4bf, 0xa4c0, 0xa4c1, 0xa4c3, 0xa4c4,
0xa4c6, 0xa4c7, 0xa4c8, 0xa4c9, 0xa4ca, 0xa4cb, 0xa4ce, 0xa4cf, 0xa4d0, 0xa4de,
0xa4df, 0xa4e1, 0xa4e2, 0xa4e4, 0xa4e8, 0xa4e9, 0xa4ea, 0xa4eb, 0xa4ec, 0xa4ef,
0xa4f2, 0xa4f3, 0xa5a2, 0xa5a3, 0xa5a4, 0xa5a6, 0xa5a7, 0xa5aa, 0xa5ad, 0xa5af,
0xa5b0, 0xa5b3, 0xa5b5, 0xa5b7, 0xa5b8, 0xa5b9, 0xa5bf, 0xa5c3, 0xa5c6, 0xa5c7,
0xa5c8, 0xa5c9, 0xa5cb, 0xa5d0, 0xa5d5, 0xa5d6, 0xa5d7, 0xa5de, 0xa5e0, 0xa5e1,
0xa5e5, 0xa5e9, 0xa5ea, 0xa5eb, 0xa5ec, 0xa5ed, 0xa5f3, 0xb8a9, 0xb9d4, 0xbaee,
0xbbc8, 0xbef0, 0xbfb7, 0xc4ea, 0xc6fc, 0xc7bd, 0xcab8, 0xcaf3, 0xcbdc, 0xcdd1};
String getName() {
return "EUC-JP";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det, commonChars);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
public String getLanguage()
{
return "ja";
}
}
/**
* The charset recognize for EUC-KR. A singleton instance of this class
* is created and kept by the public CharsetDetector class
*/
static class CharsetRecog_euc_kr extends CharsetRecog_euc {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
// of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xb0a1, 0xb0b3, 0xb0c5, 0xb0cd, 0xb0d4, 0xb0e6, 0xb0ed, 0xb0f8, 0xb0fa, 0xb0fc,
0xb1b8, 0xb1b9, 0xb1c7, 0xb1d7, 0xb1e2, 0xb3aa, 0xb3bb, 0xb4c2, 0xb4cf, 0xb4d9,
0xb4eb, 0xb5a5, 0xb5b5, 0xb5bf, 0xb5c7, 0xb5e9, 0xb6f3, 0xb7af, 0xb7c2, 0xb7ce,
0xb8a6, 0xb8ae, 0xb8b6, 0xb8b8, 0xb8bb, 0xb8e9, 0xb9ab, 0xb9ae, 0xb9cc, 0xb9ce,
0xb9fd, 0xbab8, 0xbace, 0xbad0, 0xbaf1, 0xbbe7, 0xbbf3, 0xbbfd, 0xbcad, 0xbcba,
0xbcd2, 0xbcf6, 0xbdba, 0xbdc0, 0xbdc3, 0xbdc5, 0xbec6, 0xbec8, 0xbedf, 0xbeee,
0xbef8, 0xbefa, 0xbfa1, 0xbfa9, 0xbfc0, 0xbfe4, 0xbfeb, 0xbfec, 0xbff8, 0xc0a7,
0xc0af, 0xc0b8, 0xc0ba, 0xc0bb, 0xc0bd, 0xc0c7, 0xc0cc, 0xc0ce, 0xc0cf, 0xc0d6,
0xc0da, 0xc0e5, 0xc0fb, 0xc0fc, 0xc1a4, 0xc1a6, 0xc1b6, 0xc1d6, 0xc1df, 0xc1f6,
0xc1f8, 0xc4a1, 0xc5cd, 0xc6ae, 0xc7cf, 0xc7d1, 0xc7d2, 0xc7d8, 0xc7e5, 0xc8ad};
String getName() {
return "EUC-KR";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det, commonChars);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
public String getLanguage()
{
return "ko";
}
}
}
/**
*
* GB-18030 recognizer. Uses simplified Chinese statistics.
*
*/
static class CharsetRecog_gb_18030 extends CharsetRecog_mbcs {
/*
* (non-Javadoc)
* Get the next character value for EUC based encodings.
* Character "value" is simply the raw bytes that make up the character
* packed into an int.
*/
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
int secondByte;
int thirdByte;
int fourthByte;
buildChar: {
firstByte = it.charValue = it.nextByte(det);
if (firstByte < 0) {
// Ran off the end of the input data
it.done = true;
break buildChar;
}
if (firstByte <= 0x80) {
// single byte char
break buildChar;
}
secondByte = it.nextByte(det);
it.charValue = (it.charValue << 8) | secondByte;
if (firstByte >= 0x81 && firstByte <= 0xFE) {
// Two byte Char
if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) {
break buildChar;
}
// Four byte char
if (secondByte >= 0x30 && secondByte <= 0x39) {
thirdByte = it.nextByte(det);
if (thirdByte >= 0x81 && thirdByte <= 0xFE) {
fourthByte = it.nextByte(det);
if (fourthByte >= 0x30 && fourthByte <= 0x39) {
it.charValue = (it.charValue << 16) | (thirdByte << 8) | fourthByte;
break buildChar;
}
}
}
it.error = true;
break buildChar;
}
}
return !it.done;
}
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
// of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa1a1, 0xa1a2, 0xa1a3, 0xa1a4, 0xa1b0, 0xa1b1, 0xa1f1, 0xa1f3, 0xa3a1, 0xa3ac,
0xa3ba, 0xb1a8, 0xb1b8, 0xb1be, 0xb2bb, 0xb3c9, 0xb3f6, 0xb4f3, 0xb5bd, 0xb5c4,
0xb5e3, 0xb6af, 0xb6d4, 0xb6e0, 0xb7a2, 0xb7a8, 0xb7bd, 0xb7d6, 0xb7dd, 0xb8b4,
0xb8df, 0xb8f6, 0xb9ab, 0xb9c9, 0xb9d8, 0xb9fa, 0xb9fd, 0xbacd, 0xbba7, 0xbbd6,
0xbbe1, 0xbbfa, 0xbcbc, 0xbcdb, 0xbcfe, 0xbdcc, 0xbecd, 0xbedd, 0xbfb4, 0xbfc6,
0xbfc9, 0xc0b4, 0xc0ed, 0xc1cb, 0xc2db, 0xc3c7, 0xc4dc, 0xc4ea, 0xc5cc, 0xc6f7,
0xc7f8, 0xc8ab, 0xc8cb, 0xc8d5, 0xc8e7, 0xc9cf, 0xc9fa, 0xcab1, 0xcab5, 0xcac7,
0xcad0, 0xcad6, 0xcaf5, 0xcafd, 0xccec, 0xcdf8, 0xceaa, 0xcec4, 0xced2, 0xcee5,
0xcfb5, 0xcfc2, 0xcfd6, 0xd0c2, 0xd0c5, 0xd0d0, 0xd0d4, 0xd1a7, 0xd2aa, 0xd2b2,
0xd2b5, 0xd2bb, 0xd2d4, 0xd3c3, 0xd3d0, 0xd3fd, 0xd4c2, 0xd4da, 0xd5e2, 0xd6d0};
String getName() {
return "GB18030";
}
CharsetMatch match(CharsetDetector det) {
int confidence = match(det, commonChars);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
public String getLanguage()
{
return "zh";
}
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecog_sbcs.java
================================================
/*
****************************************************************************
* Copyright (C) 2005-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
************************************************************************** *
*
* Modified by Oleg Trifonov
* added cp866 support
*/
package com.ibm.icu.text;
/**
* This class recognizes single-byte encodings. Because the encoding scheme is so
* simple, language statistics are used to do the matching.
*/
abstract class CharsetRecog_sbcs extends CharsetRecognizer {
/* (non-Javadoc)
* @see com.ibm.icu.text.CharsetRecognizer#getName()
*/
abstract String getName();
static class NGramParser
{
// private static final int N_GRAM_SIZE = 3;
private static final int N_GRAM_MASK = 0xFFFFFF;
protected int byteIndex = 0;
private int ngram;
private final int[] ngramList;
protected byte[] byteMap;
private int ngramCount;
private int hitCount;
protected byte spaceChar;
public NGramParser(int[] theNgramList, byte[] theByteMap)
{
ngramList = theNgramList;
byteMap = theByteMap;
ngram = 0;
ngramCount = hitCount = 0;
}
/*
* Binary search for value in table, which must have exactly 64 entries.
*/
private static int search(int[] table, int value)
{
int index = 0;
if (table[index + 32] <= value) {
index += 32;
}
if (table[index + 16] <= value) {
index += 16;
}
if (table[index + 8] <= value) {
index += 8;
}
if (table[index + 4] <= value) {
index += 4;
}
if (table[index + 2] <= value) {
index += 2;
}
if (table[index + 1] <= value) {
index += 1;
}
if (table[index] > value) {
index -= 1;
}
if (index < 0 || table[index] != value) {
return -1;
}
return index;
}
private void lookup(int thisNgram)
{
ngramCount += 1;
if (search(ngramList, thisNgram) >= 0) {
hitCount += 1;
}
}
protected void addByte(int b)
{
ngram = ((ngram << 8) + (b & 0xFF)) & N_GRAM_MASK;
lookup(ngram);
}
private int nextByte(CharsetDetector det)
{
if (byteIndex >= det.fInputLen) {
return -1;
}
return det.fInputBytes[byteIndex++] & 0xFF;
}
protected void parseCharacters(CharsetDetector det)
{
int b;
boolean ignoreSpace = false;
while ((b = nextByte(det)) >= 0) {
byte mb = byteMap[b];
// TODO: 0x20 might not be a space in all character sets...
if (mb != 0) {
if (!(mb == spaceChar && ignoreSpace)) {
addByte(mb);
}
ignoreSpace = (mb == spaceChar);
}
}
}
public int parse(CharsetDetector det)
{
return parse (det, (byte)0x20);
}
public int parse(CharsetDetector det, byte spaceCh)
{
this.spaceChar = spaceCh;
parseCharacters(det);
// TODO: Is this OK? The buffer could have ended in the middle of a word...
addByte(spaceChar);
double rawPercent = (double) hitCount / (double) ngramCount;
// if (rawPercent <= 2.0) {
// return 0;
// }
// TODO - This is a bit of a hack to take care of a case
// were we were getting a confidence of 135...
if (rawPercent > 0.33) {
return 98;
}
return (int) (rawPercent * 300.0);
}
}
static class NGramParser_IBM420 extends NGramParser
{
private byte alef = 0x00;
protected static byte[] unshapeMap = {
/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F */
/* 0- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 1- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 2- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 3- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 4- */ (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x42, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x47, (byte) 0x49, (byte) 0x4A, (byte) 0x4B, (byte) 0x4C, (byte) 0x4D, (byte) 0x4E, (byte) 0x4F,
/* 5- */ (byte) 0x50, (byte) 0x49, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x56, (byte) 0x58, (byte) 0x58, (byte) 0x5A, (byte) 0x5B, (byte) 0x5C, (byte) 0x5D, (byte) 0x5E, (byte) 0x5F,
/* 6- */ (byte) 0x60, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x63, (byte) 0x65, (byte) 0x65, (byte) 0x67, (byte) 0x67, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
/* 7- */ (byte) 0x69, (byte) 0x71, (byte) 0x71, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x77, (byte) 0x79, (byte) 0x7A, (byte) 0x7B, (byte) 0x7C, (byte) 0x7D, (byte) 0x7E, (byte) 0x7F,
/* 8- */ (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x80, (byte) 0x8B, (byte) 0x8B, (byte) 0x8D, (byte) 0x8D, (byte) 0x8F,
/* 9- */ (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9E, (byte) 0x9E,
/* A- */ (byte) 0x9E, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x9E, (byte) 0xAB, (byte) 0xAB, (byte) 0xAD, (byte) 0xAD, (byte) 0xAF,
/* B- */ (byte) 0xAF, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xB1, (byte) 0xBB, (byte) 0xBB, (byte) 0xBD, (byte) 0xBD, (byte) 0xBF,
/* C- */ (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xBF, (byte) 0xCC, (byte) 0xBF, (byte) 0xCE, (byte) 0xCF,
/* D- */ (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDA, (byte) 0xDC, (byte) 0xDC, (byte) 0xDC, (byte) 0xDF,
/* E- */ (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* F- */ (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,
};
public NGramParser_IBM420(int[] theNgramList, byte[] theByteMap)
{
super(theNgramList, theByteMap);
}
private byte isLamAlef(byte b) {
if(b == (byte)0xb2 || b == (byte)0xb3){
return (byte)0x47;
}else if(b == (byte)0xb4 || b == (byte)0xb5){
return (byte)0x49;
}else if(b == (byte)0xb8 || b == (byte)0xb9){
return (byte)0x56;
}else
return (byte)0x00;
}
/*
* Arabic shaping needs to be done manually. Cannot call ArabicShaping class
* because CharsetDetector is dealing with bytes not Unicode code points. We could
* convert the bytes to Unicode code points but that would leave us dependent
* on CharsetICU which we try to avoid. IBM420 converter amongst different versions
* of JDK can produce different results and therefore is also avoided.
*/
private int nextByte(CharsetDetector det)
{
if (byteIndex >= det.fInputLen || det.fInputBytes[byteIndex] == 0) {
return -1;
}
int next;
alef = isLamAlef(det.fInputBytes[byteIndex]);
if(alef != (byte)0x00)
next = 0xB1 & 0xFF;
else
next = unshapeMap[det.fInputBytes[byteIndex]& 0xFF] & 0xFF;
byteIndex++;
return next;
}
protected void parseCharacters(CharsetDetector det)
{
int b;
boolean ignoreSpace = false;
while ((b = nextByte(det)) >= 0) {
byte mb = byteMap[b];
// TODO: 0x20 might not be a space in all character sets...
if (mb != 0) {
if (!(mb == spaceChar && ignoreSpace)) {
addByte(mb);
}
ignoreSpace = (mb == spaceChar);
}
if(alef != (byte)0x00){
mb = byteMap[alef & 0xFF];
// TODO: 0x20 might not be a space in all character sets...
if (mb != 0) {
if (!(mb == spaceChar && ignoreSpace)) {
addByte(mb);
}
ignoreSpace = (mb == spaceChar);
}
}
}
}
}
int match(CharsetDetector det, int[] ngrams, byte[] byteMap)
{
return match(det, ngrams, byteMap, (byte)0x20);
}
int match(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar) {
NGramParser parser = new NGramParser(ngrams, byteMap);
return parser.parse(det, spaceChar);
}
int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar){
NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);
return parser.parse(det, spaceChar);
}
static class NGramsPlusLang {
int[] fNGrams;
String fLang;
NGramsPlusLang(String la, int [] ng) {
fLang = la;
fNGrams = ng;
}
}
static class CharsetRecog_8859_1 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,
};
private static final NGramsPlusLang[] ngrams_8859_1 = new NGramsPlusLang[] {
new NGramsPlusLang(
"da",
new int[] {
// ' af'
0x206166, 0x206174, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207369, 0x207374, 0x207469, 0x207669, 0x616620,
0x616E20, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646572, 0x646574, 0x652073, 0x656420, 0x656465, 0x656E20, 0x656E64, 0x657220, 0x657265, 0x657320,
0x657420, 0x666F72, 0x676520, 0x67656E, 0x676572, 0x696765, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6572, 0x6C6967, 0x6C6C65, 0x6D6564, 0x6E6465, 0x6E6520,
0x6E6720, 0x6E6765, 0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722064, 0x722065, 0x722073, 0x726520, 0x737465, 0x742073, 0x746520, 0x746572, 0x74696C, 0x766572,
}),
new NGramsPlusLang(
"de",
new int[] {
// ' an'
0x20616E, 0x206175, 0x206265, 0x206461, 0x206465, 0x206469, 0x206569, 0x206765, 0x206861, 0x20696E, 0x206D69, 0x207363, 0x207365, 0x20756E, 0x207665, 0x20766F,
0x207765, 0x207A75, 0x626572, 0x636820, 0x636865, 0x636874, 0x646173, 0x64656E, 0x646572, 0x646965, 0x652064, 0x652073, 0x65696E, 0x656974, 0x656E20, 0x657220,
0x657320, 0x67656E, 0x68656E, 0x687420, 0x696368, 0x696520, 0x696E20, 0x696E65, 0x697420, 0x6C6963, 0x6C6C65, 0x6E2061, 0x6E2064, 0x6E2073, 0x6E6420, 0x6E6465,
0x6E6520, 0x6E6720, 0x6E6765, 0x6E7465, 0x722064, 0x726465, 0x726569, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x756E64, 0x756E67, 0x766572,
}),
new NGramsPlusLang(
"en",
new int[] {
0x206120, 0x20616E, 0x206265, 0x20636F, 0x20666F, 0x206861, 0x206865, 0x20696E, 0x206D61, 0x206F66, 0x207072, 0x207265, 0x207361, 0x207374, 0x207468, 0x20746F,
0x207768, 0x616964, 0x616C20, 0x616E20, 0x616E64, 0x617320, 0x617420, 0x617465, 0x617469, 0x642061, 0x642074, 0x652061, 0x652073, 0x652074, 0x656420, 0x656E74,
0x657220, 0x657320, 0x666F72, 0x686174, 0x686520, 0x686572, 0x696420, 0x696E20, 0x696E67, 0x696F6E, 0x697320, 0x6E2061, 0x6E2074, 0x6E6420, 0x6E6720, 0x6E7420,
0x6F6620, 0x6F6E20, 0x6F7220, 0x726520, 0x727320, 0x732061, 0x732074, 0x736169, 0x737420, 0x742074, 0x746572, 0x746861, 0x746865, 0x74696F, 0x746F20, 0x747320,
}),
new NGramsPlusLang(
"es",
new int[] {
0x206120, 0x206361, 0x20636F, 0x206465, 0x20656C, 0x20656E, 0x206573, 0x20696E, 0x206C61, 0x206C6F, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365,
0x20756E, 0x207920, 0x612063, 0x612064, 0x612065, 0x61206C, 0x612070, 0x616369, 0x61646F, 0x616C20, 0x617220, 0x617320, 0x6369F3, 0x636F6E, 0x646520, 0x64656C,
0x646F20, 0x652064, 0x652065, 0x65206C, 0x656C20, 0x656E20, 0x656E74, 0x657320, 0x657374, 0x69656E, 0x69F36E, 0x6C6120, 0x6C6F73, 0x6E2065, 0x6E7465, 0x6F2064,
0x6F2065, 0x6F6E20, 0x6F7220, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732064, 0x732065, 0x732070, 0x736520, 0x746520, 0x746F20, 0x756520, 0xF36E20,
}),
new NGramsPlusLang(
"fr",
new int[] {
0x206175, 0x20636F, 0x206461, 0x206465, 0x206475, 0x20656E, 0x206574, 0x206C61, 0x206C65, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207365, 0x20736F, 0x20756E,
0x20E020, 0x616E74, 0x617469, 0x636520, 0x636F6E, 0x646520, 0x646573, 0x647520, 0x652061, 0x652063, 0x652064, 0x652065, 0x65206C, 0x652070, 0x652073, 0x656E20,
0x656E74, 0x657220, 0x657320, 0x657420, 0x657572, 0x696F6E, 0x697320, 0x697420, 0x6C6120, 0x6C6520, 0x6C6573, 0x6D656E, 0x6E2064, 0x6E6520, 0x6E7320, 0x6E7420,
0x6F6E20, 0x6F6E74, 0x6F7572, 0x717565, 0x72206C, 0x726520, 0x732061, 0x732064, 0x732065, 0x73206C, 0x732070, 0x742064, 0x746520, 0x74696F, 0x756520, 0x757220,
}),
new NGramsPlusLang(
"it",
new int[] {
0x20616C, 0x206368, 0x20636F, 0x206465, 0x206469, 0x206520, 0x20696C, 0x20696E, 0x206C61, 0x207065, 0x207072, 0x20756E, 0x612063, 0x612064, 0x612070, 0x612073,
0x61746F, 0x636865, 0x636F6E, 0x64656C, 0x646920, 0x652061, 0x652063, 0x652064, 0x652069, 0x65206C, 0x652070, 0x652073, 0x656C20, 0x656C6C, 0x656E74, 0x657220,
0x686520, 0x692061, 0x692063, 0x692064, 0x692073, 0x696120, 0x696C20, 0x696E20, 0x696F6E, 0x6C6120, 0x6C6520, 0x6C6920, 0x6C6C61, 0x6E6520, 0x6E6920, 0x6E6F20,
0x6E7465, 0x6F2061, 0x6F2064, 0x6F2069, 0x6F2073, 0x6F6E20, 0x6F6E65, 0x706572, 0x726120, 0x726520, 0x736920, 0x746120, 0x746520, 0x746920, 0x746F20, 0x7A696F,
}),
new NGramsPlusLang(
"nl",
new int[] {
0x20616C, 0x206265, 0x206461, 0x206465, 0x206469, 0x206565, 0x20656E, 0x206765, 0x206865, 0x20696E, 0x206D61, 0x206D65, 0x206F70, 0x207465, 0x207661, 0x207665,
0x20766F, 0x207765, 0x207A69, 0x61616E, 0x616172, 0x616E20, 0x616E64, 0x617220, 0x617420, 0x636874, 0x646520, 0x64656E, 0x646572, 0x652062, 0x652076, 0x65656E,
0x656572, 0x656E20, 0x657220, 0x657273, 0x657420, 0x67656E, 0x686574, 0x696520, 0x696E20, 0x696E67, 0x697320, 0x6E2062, 0x6E2064, 0x6E2065, 0x6E2068, 0x6E206F,
0x6E2076, 0x6E6465, 0x6E6720, 0x6F6E64, 0x6F6F72, 0x6F7020, 0x6F7220, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x76616E, 0x766572, 0x766F6F,
}),
new NGramsPlusLang(
"no",
new int[] {
0x206174, 0x206176, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207365, 0x20736B, 0x20736F, 0x207374, 0x207469,
0x207669, 0x20E520, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646574, 0x652073, 0x656420, 0x656E20, 0x656E65, 0x657220, 0x657265, 0x657420, 0x657474,
0x666F72, 0x67656E, 0x696B6B, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6520, 0x6C6C65, 0x6D6564, 0x6D656E, 0x6E2073, 0x6E6520, 0x6E6720, 0x6E6765, 0x6E6E65,
0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722073, 0x726520, 0x736F6D, 0x737465, 0x742073, 0x746520, 0x74656E, 0x746572, 0x74696C, 0x747420, 0x747465, 0x766572,
}),
new NGramsPlusLang(
"pt",
new int[] {
0x206120, 0x20636F, 0x206461, 0x206465, 0x20646F, 0x206520, 0x206573, 0x206D61, 0x206E6F, 0x206F20, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365,
0x20756D, 0x612061, 0x612063, 0x612064, 0x612070, 0x616465, 0x61646F, 0x616C20, 0x617220, 0x617261, 0x617320, 0x636F6D, 0x636F6E, 0x646120, 0x646520, 0x646F20,
0x646F73, 0x652061, 0x652064, 0x656D20, 0x656E74, 0x657320, 0x657374, 0x696120, 0x696361, 0x6D656E, 0x6E7465, 0x6E746F, 0x6F2061, 0x6F2063, 0x6F2064, 0x6F2065,
0x6F2070, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732061, 0x732064, 0x732065, 0x732070, 0x737461, 0x746520, 0x746F20, 0x756520, 0xE36F20, 0xE7E36F,
}),
new NGramsPlusLang(
"sv",
new int[] {
0x206174, 0x206176, 0x206465, 0x20656E, 0x2066F6, 0x206861, 0x206920, 0x20696E, 0x206B6F, 0x206D65, 0x206F63, 0x2070E5, 0x20736B, 0x20736F, 0x207374, 0x207469,
0x207661, 0x207669, 0x20E472, 0x616465, 0x616E20, 0x616E64, 0x617220, 0x617474, 0x636820, 0x646520, 0x64656E, 0x646572, 0x646574, 0x656420, 0x656E20, 0x657220,
0x657420, 0x66F672, 0x67656E, 0x696C6C, 0x696E67, 0x6B6120, 0x6C6C20, 0x6D6564, 0x6E2073, 0x6E6120, 0x6E6465, 0x6E6720, 0x6E6765, 0x6E696E, 0x6F6368, 0x6F6D20,
0x6F6E20, 0x70E520, 0x722061, 0x722073, 0x726120, 0x736B61, 0x736F6D, 0x742073, 0x746120, 0x746520, 0x746572, 0x74696C, 0x747420, 0x766172, 0xE47220, 0xF67220,
}),
};
public CharsetMatch match(CharsetDetector det)
{
String name = det.fC1Bytes ? "windows-1252" : "ISO-8859-1";
int bestConfidenceSoFar = -1;
String lang = null;
for (NGramsPlusLang ngl: ngrams_8859_1) {
int confidence = match(det, ngl.fNGrams, byteMap);
if (confidence > bestConfidenceSoFar) {
bestConfidenceSoFar = confidence;
lang = ngl.fLang;
}
}
return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, bestConfidenceSoFar, name, lang);
}
public String getName()
{
return "ISO-8859-1";
}
}
static class CharsetRecog_8859_2 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0x20,
(byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF,
(byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7,
(byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20,
};
private static final NGramsPlusLang[] ngrams_8859_2 = new NGramsPlusLang[] {
new NGramsPlusLang(
"cs",
new int[] {
0x206120, 0x206279, 0x20646F, 0x206A65, 0x206E61, 0x206E65, 0x206F20, 0x206F64, 0x20706F, 0x207072, 0x2070F8, 0x20726F, 0x207365, 0x20736F, 0x207374, 0x20746F,
0x207620, 0x207679, 0x207A61, 0x612070, 0x636520, 0x636820, 0x652070, 0x652073, 0x652076, 0x656D20, 0x656EED, 0x686F20, 0x686F64, 0x697374, 0x6A6520, 0x6B7465,
0x6C6520, 0x6C6920, 0x6E6120, 0x6EE920, 0x6EEC20, 0x6EED20, 0x6F2070, 0x6F646E, 0x6F6A69, 0x6F7374, 0x6F7520, 0x6F7661, 0x706F64, 0x706F6A, 0x70726F, 0x70F865,
0x736520, 0x736F75, 0x737461, 0x737469, 0x73746E, 0x746572, 0x746EED, 0x746F20, 0x752070, 0xBE6520, 0xE16EED, 0xE9686F, 0xED2070, 0xED2073, 0xED6D20, 0xF86564,
}),
new NGramsPlusLang(
"hu",
new int[] {
0x206120, 0x20617A, 0x206265, 0x206567, 0x20656C, 0x206665, 0x206861, 0x20686F, 0x206973, 0x206B65, 0x206B69, 0x206BF6, 0x206C65, 0x206D61, 0x206D65, 0x206D69,
0x206E65, 0x20737A, 0x207465, 0x20E973, 0x612061, 0x61206B, 0x61206D, 0x612073, 0x616B20, 0x616E20, 0x617A20, 0x62616E, 0x62656E, 0x656779, 0x656B20, 0x656C20,
0x656C65, 0x656D20, 0x656E20, 0x657265, 0x657420, 0x657465, 0x657474, 0x677920, 0x686F67, 0x696E74, 0x697320, 0x6B2061, 0x6BF67A, 0x6D6567, 0x6D696E, 0x6E2061,
0x6E616B, 0x6E656B, 0x6E656D, 0x6E7420, 0x6F6779, 0x732061, 0x737A65, 0x737A74, 0x737AE1, 0x73E967, 0x742061, 0x747420, 0x74E173, 0x7A6572, 0xE16E20, 0xE97320,
}),
new NGramsPlusLang(
"pl",
new int[] {
0x20637A, 0x20646F, 0x206920, 0x206A65, 0x206B6F, 0x206D61, 0x206D69, 0x206E61, 0x206E69, 0x206F64, 0x20706F, 0x207072, 0x207369, 0x207720, 0x207769, 0x207779,
0x207A20, 0x207A61, 0x612070, 0x612077, 0x616E69, 0x636820, 0x637A65, 0x637A79, 0x646F20, 0x647A69, 0x652070, 0x652073, 0x652077, 0x65207A, 0x65676F, 0x656A20,
0x656D20, 0x656E69, 0x676F20, 0x696120, 0x696520, 0x69656A, 0x6B6120, 0x6B6920, 0x6B6965, 0x6D6965, 0x6E6120, 0x6E6961, 0x6E6965, 0x6F2070, 0x6F7761, 0x6F7769,
0x706F6C, 0x707261, 0x70726F, 0x70727A, 0x727A65, 0x727A79, 0x7369EA, 0x736B69, 0x737461, 0x776965, 0x796368, 0x796D20, 0x7A6520, 0x7A6965, 0x7A7920, 0xF37720,
}),
new NGramsPlusLang(
"ro",
new int[] {
0x206120, 0x206163, 0x206361, 0x206365, 0x20636F, 0x206375, 0x206465, 0x206469, 0x206C61, 0x206D61, 0x207065, 0x207072, 0x207365, 0x2073E3, 0x20756E, 0x20BA69,
0x20EE6E, 0x612063, 0x612064, 0x617265, 0x617420, 0x617465, 0x617520, 0x636172, 0x636F6E, 0x637520, 0x63E320, 0x646520, 0x652061, 0x652063, 0x652064, 0x652070,
0x652073, 0x656120, 0x656920, 0x656C65, 0x656E74, 0x657374, 0x692061, 0x692063, 0x692064, 0x692070, 0x696520, 0x696920, 0x696E20, 0x6C6120, 0x6C6520, 0x6C6F72,
0x6C7569, 0x6E6520, 0x6E7472, 0x6F7220, 0x70656E, 0x726520, 0x726561, 0x727520, 0x73E320, 0x746520, 0x747275, 0x74E320, 0x756920, 0x756C20, 0xBA6920, 0xEE6E20,
})
};
public CharsetMatch match(CharsetDetector det)
{
String name = det.fC1Bytes ? "windows-1250" : "ISO-8859-2";
int bestConfidenceSoFar = -1;
String lang = null;
for (NGramsPlusLang ngl: ngrams_8859_2) {
int confidence = match(det, ngl.fNGrams, byteMap);
if (confidence > bestConfidenceSoFar) {
bestConfidenceSoFar = confidence;
lang = ngl.fLang;
}
}
return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, bestConfidenceSoFar, name, lang);
}
public String getName()
{
return "ISO-8859-2";
}
}
abstract static class CharsetRecog_8859_5 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF,
};
public String getName()
{
return "ISO-8859-5";
}
}
static class CharsetRecog_8859_5_ru extends CharsetRecog_8859_5
{
private static final int[] ngrams = {
0x20D220, 0x20D2DE, 0x20D4DE, 0x20D7D0, 0x20D820, 0x20DAD0, 0x20DADE, 0x20DDD0, 0x20DDD5, 0x20DED1, 0x20DFDE, 0x20DFE0, 0x20E0D0, 0x20E1DE, 0x20E1E2, 0x20E2DE,
0x20E7E2, 0x20EDE2, 0xD0DDD8, 0xD0E2EC, 0xD3DE20, 0xD5DBEC, 0xD5DDD8, 0xD5E1E2, 0xD5E220, 0xD820DF, 0xD8D520, 0xD8D820, 0xD8EF20, 0xDBD5DD, 0xDBD820, 0xDBECDD,
0xDDD020, 0xDDD520, 0xDDD8D5, 0xDDD8EF, 0xDDDE20, 0xDDDED2, 0xDE20D2, 0xDE20DF, 0xDE20E1, 0xDED220, 0xDED2D0, 0xDED3DE, 0xDED920, 0xDEDBEC, 0xDEDC20, 0xDEE1E2,
0xDFDEDB, 0xDFE0D5, 0xDFE0D8, 0xDFE0DE, 0xE0D0D2, 0xE0D5D4, 0xE1E2D0, 0xE1E2D2, 0xE1E2D8, 0xE1EF20, 0xE2D5DB, 0xE2DE20, 0xE2DEE0, 0xE2EC20, 0xE7E2DE, 0xEBE520,
};
public String getLanguage()
{
return "ru";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
abstract static class CharsetRecog_8859_6 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,
(byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
};
public String getName()
{
return "ISO-8859-6";
}
}
static class CharsetRecog_8859_6_ar extends CharsetRecog_8859_6
{
private static final int[] ngrams = {
0x20C7E4, 0x20C7E6, 0x20C8C7, 0x20D9E4, 0x20E1EA, 0x20E4E4, 0x20E5E6, 0x20E8C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E420, 0xC7E4C3, 0xC7E4C7, 0xC7E4C8,
0xC7E4CA, 0xC7E4CC, 0xC7E4CD, 0xC7E4CF, 0xC7E4D3, 0xC7E4D9, 0xC7E4E2, 0xC7E4E5, 0xC7E4E8, 0xC7E4EA, 0xC7E520, 0xC7E620, 0xC7E6CA, 0xC820C7, 0xC920C7, 0xC920E1,
0xC920E4, 0xC920E5, 0xC920E8, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xD920C7, 0xD9E4E9, 0xE1EA20, 0xE420C7, 0xE4C920, 0xE4E920, 0xE4EA20,
0xE520C7, 0xE5C720, 0xE5C920, 0xE5E620, 0xE620C7, 0xE720C7, 0xE7C720, 0xE8C7E4, 0xE8E620, 0xE920C7, 0xEA20C7, 0xEA20E5, 0xEA20E8, 0xEAC920, 0xEAD120, 0xEAE620,
};
public String getLanguage()
{
return "ar";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
abstract static class CharsetRecog_8859_7 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0xA1, (byte) 0xA2, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xDC, (byte) 0x20,
(byte) 0xDD, (byte) 0xDE, (byte) 0xDF, (byte) 0x20, (byte) 0xFC, (byte) 0x20, (byte) 0xFD, (byte) 0xFE,
(byte) 0xC0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0x20, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20,
};
public String getName()
{
return "ISO-8859-7";
}
}
static class CharsetRecog_8859_7_el extends CharsetRecog_8859_7
{
private static final int[] ngrams = {
0x20E1ED, 0x20E1F0, 0x20E3E9, 0x20E4E9, 0x20E5F0, 0x20E720, 0x20EAE1, 0x20ECE5, 0x20EDE1, 0x20EF20, 0x20F0E1, 0x20F0EF, 0x20F0F1, 0x20F3F4, 0x20F3F5, 0x20F4E7,
0x20F4EF, 0xDFE120, 0xE120E1, 0xE120F4, 0xE1E920, 0xE1ED20, 0xE1F0FC, 0xE1F220, 0xE3E9E1, 0xE5E920, 0xE5F220, 0xE720F4, 0xE7ED20, 0xE7F220, 0xE920F4, 0xE9E120,
0xE9EADE, 0xE9F220, 0xEAE1E9, 0xEAE1F4, 0xECE520, 0xED20E1, 0xED20E5, 0xED20F0, 0xEDE120, 0xEFF220, 0xEFF520, 0xF0EFF5, 0xF0F1EF, 0xF0FC20, 0xF220E1, 0xF220E5,
0xF220EA, 0xF220F0, 0xF220F4, 0xF3E520, 0xF3E720, 0xF3F4EF, 0xF4E120, 0xF4E1E9, 0xF4E7ED, 0xF4E7F2, 0xF4E9EA, 0xF4EF20, 0xF4EFF5, 0xF4F9ED, 0xF9ED20, 0xFEED20,
};
public String getLanguage()
{
return "el";
}
public CharsetMatch match(CharsetDetector det)
{
String name = det.fC1Bytes ? "windows-1253" : "ISO-8859-7";
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, confidence, name, "el");
}
}
abstract static class CharsetRecog_8859_8 extends CharsetRecog_sbcs {
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
};
public String getName()
{
return "ISO-8859-8";
}
}
static class CharsetRecog_8859_8_I_he extends CharsetRecog_8859_8
{
private static final int[] ngrams = {
0x20E0E5, 0x20E0E7, 0x20E0E9, 0x20E0FA, 0x20E1E9, 0x20E1EE, 0x20E4E0, 0x20E4E5, 0x20E4E9, 0x20E4EE, 0x20E4F2, 0x20E4F9, 0x20E4FA, 0x20ECE0, 0x20ECE4, 0x20EEE0,
0x20F2EC, 0x20F9EC, 0xE0FA20, 0xE420E0, 0xE420E1, 0xE420E4, 0xE420EC, 0xE420EE, 0xE420F9, 0xE4E5E0, 0xE5E020, 0xE5ED20, 0xE5EF20, 0xE5F820, 0xE5FA20, 0xE920E4,
0xE9E420, 0xE9E5FA, 0xE9E9ED, 0xE9ED20, 0xE9EF20, 0xE9F820, 0xE9FA20, 0xEC20E0, 0xEC20E4, 0xECE020, 0xECE420, 0xED20E0, 0xED20E1, 0xED20E4, 0xED20EC, 0xED20EE,
0xED20F9, 0xEEE420, 0xEF20E4, 0xF0E420, 0xF0E920, 0xF0E9ED, 0xF2EC20, 0xF820E4, 0xF8E9ED, 0xF9EC20, 0xFA20E0, 0xFA20E1, 0xFA20E4, 0xFA20EC, 0xFA20EE, 0xFA20F9,
};
public String getName()
{
return "ISO-8859-8-I";
}
public String getLanguage()
{
return "he";
}
public CharsetMatch match(CharsetDetector det) {
String name = det.fC1Bytes ? "windows-1255" : "ISO-8859-8-I";
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, confidence, name, "he");
}
}
static class CharsetRecog_8859_8_he extends CharsetRecog_8859_8
{
private static final int[] ngrams = {
0x20E0E5, 0x20E0EC, 0x20E4E9, 0x20E4EC, 0x20E4EE, 0x20E4F0, 0x20E9F0, 0x20ECF2, 0x20ECF9, 0x20EDE5, 0x20EDE9, 0x20EFE5, 0x20EFE9, 0x20F8E5, 0x20F8E9, 0x20FAE0,
0x20FAE5, 0x20FAE9, 0xE020E4, 0xE020EC, 0xE020ED, 0xE020FA, 0xE0E420, 0xE0E5E4, 0xE0EC20, 0xE0EE20, 0xE120E4, 0xE120ED, 0xE120FA, 0xE420E4, 0xE420E9, 0xE420EC,
0xE420ED, 0xE420EF, 0xE420F8, 0xE420FA, 0xE4EC20, 0xE5E020, 0xE5E420, 0xE7E020, 0xE9E020, 0xE9E120, 0xE9E420, 0xEC20E4, 0xEC20ED, 0xEC20FA, 0xECF220, 0xECF920,
0xEDE9E9, 0xEDE9F0, 0xEDE9F8, 0xEE20E4, 0xEE20ED, 0xEE20FA, 0xEEE120, 0xEEE420, 0xF2E420, 0xF920E4, 0xF920ED, 0xF920FA, 0xF9E420, 0xFAE020, 0xFAE420, 0xFAE5E9,
};
public String getLanguage()
{
return "he";
}
public CharsetMatch match(CharsetDetector det)
{
String name = det.fC1Bytes ? "windows-1255" : "ISO-8859-8";
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, confidence, name, "he");
}
}
abstract static class CharsetRecog_8859_9 extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x69, (byte) 0xFE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,
(byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,
};
public String getName()
{
return "ISO-8859-9";
}
}
static class CharsetRecog_8859_9_tr extends CharsetRecog_8859_9
{
private static final int[] ngrams = {
0x206261, 0x206269, 0x206275, 0x206461, 0x206465, 0x206765, 0x206861, 0x20696C, 0x206B61, 0x206B6F, 0x206D61, 0x206F6C, 0x207361, 0x207461, 0x207665, 0x207961,
0x612062, 0x616B20, 0x616C61, 0x616D61, 0x616E20, 0x616EFD, 0x617220, 0x617261, 0x6172FD, 0x6173FD, 0x617961, 0x626972, 0x646120, 0x646520, 0x646920, 0x652062,
0x65206B, 0x656469, 0x656E20, 0x657220, 0x657269, 0x657369, 0x696C65, 0x696E20, 0x696E69, 0x697220, 0x6C616E, 0x6C6172, 0x6C6520, 0x6C6572, 0x6E2061, 0x6E2062,
0x6E206B, 0x6E6461, 0x6E6465, 0x6E6520, 0x6E6920, 0x6E696E, 0x6EFD20, 0x72696E, 0x72FD6E, 0x766520, 0x796120, 0x796F72, 0xFD6E20, 0xFD6E64, 0xFD6EFD, 0xFDF0FD,
};
public String getLanguage()
{
return "tr";
}
public CharsetMatch match(CharsetDetector det)
{
String name = det.fC1Bytes ? "windows-1254" : "ISO-8859-9";
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, confidence, name, "tr");
}
}
static class CharsetRecog_windows_1251 extends CharsetRecog_sbcs
{
private static final int[] ngrams = {
// ' в ' ' во' ' до' ' за' ' и ' ' ка' ' ко' ' на' ' не' ' об' ' по' ' пр' ' ра' ' со' ' ст' ' то'
0x20E220, 0x20E2EE, 0x20E4EE, 0x20E7E0, 0x20E820, 0x20EAE0, 0x20EAEE, 0x20EDE0, 0x20EDE5, 0x20EEE1, 0x20EFEE, 0x20EFF0, 0x20F0E0, 0x20F1EE, 0x20F1F2, 0x20F2EE,
// ' чт' ' эт' 'ани' 'ать' 'го ' 'ель' 'ени' 'ест' 'ет ' 'и п' 'ие ' 'ии ' 'ия ' 'лен' 'ли ' 'льн'
0x20F7F2, 0x20FDF2, 0xE0EDE8, 0xE0F2FC, 0xE3EE20, 0xE5EBFC, 0xE5EDE8, 0xE5F1F2, 0xE5F220, 0xE820EF, 0xE8E520, 0xE8E820, 0xE8FF20, 0xEBE5ED, 0xEBE820, 0xEBFCED,
// 'на ' 'не ' 'ние' 'ния' 'но ' 'нов' 'о в' 'о п' 'о с' 'ов ' 'ова' 'ого' 'ой ' 'оль' 'ом ' 'ост'
0xEDE020, 0xEDE520, 0xEDE8E5, 0xEDE8FF, 0xEDEE20, 0xEDEEE2, 0xEE20E2, 0xEE20EF, 0xEE20F1, 0xEEE220, 0xEEE2E0, 0xEEE3EE, 0xEEE920, 0xEEEBFC, 0xEEEC20, 0xEEF1F2,
// 'пол' 'пре' 'при' 'про' 'рав' 'ред' 'ста' 'ств' 'сти' 'ся ' 'тел' 'то ' 'тор' 'ть ' 'что' 'ых '
0xEFEEEB, 0xEFF0E5, 0xEFF0E8, 0xEFF0EE, 0xF0E0E2, 0xF0E5E4, 0xF1F2E0, 0xF1F2E2, 0xF1F2E8, 0xF1FF20, 0xF2E5EB, 0xF2EE20, 0xF2EEF0, 0xF2FC20, 0xF7F2EE, 0xFBF520,
};
private static final byte[] byteMap = {
/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F */
/* 0- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 1- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 2- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 3- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 4- */ (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
/* 5- */ (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 6- */ (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
/* 7- */ (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 8- */ (byte) 0x90, (byte) 0x83, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,
/* 9- */ (byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA8, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,
/* A- */ (byte) 0x20, (byte) 0xA2, (byte) 0xA2, (byte) 0xBC, (byte) 0x20, (byte) 0xB4, (byte) 0x20, (byte) 0x20, (byte) 0xA8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xBF,
/* B- */ (byte) 0x20, (byte) 0x20, (byte) 0xB3, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x20, (byte) 0x20, (byte) 0xB8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0xBC, (byte) 0xBE, (byte) 0xBE, (byte) 0xBF,
/* C- */ (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* D- */ (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,
/* E- */ (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* F- */ (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,
};
public String getName()
{
return "windows-1251";
}
public String getLanguage()
{
return "ru";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_windows_1256 extends CharsetRecog_sbcs
{
private static final int[] ngrams = {
0x20C7E1, 0x20C7E4, 0x20C8C7, 0x20DAE1, 0x20DDED, 0x20E1E1, 0x20E3E4, 0x20E6C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E120, 0xC7E1C3, 0xC7E1C7, 0xC7E1C8,
0xC7E1CA, 0xC7E1CC, 0xC7E1CD, 0xC7E1CF, 0xC7E1D3, 0xC7E1DA, 0xC7E1DE, 0xC7E1E3, 0xC7E1E6, 0xC7E1ED, 0xC7E320, 0xC7E420, 0xC7E4CA, 0xC820C7, 0xC920C7, 0xC920DD,
0xC920E1, 0xC920E3, 0xC920E6, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xDA20C7, 0xDAE1EC, 0xDDED20, 0xE120C7, 0xE1C920, 0xE1EC20, 0xE1ED20,
0xE320C7, 0xE3C720, 0xE3C920, 0xE3E420, 0xE420C7, 0xE520C7, 0xE5C720, 0xE6C7E1, 0xE6E420, 0xEC20C7, 0xED20C7, 0xED20E3, 0xED20E6, 0xEDC920, 0xEDD120, 0xEDE420,
};
private static final byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x81, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x88, (byte) 0x20, (byte) 0x8A, (byte) 0x20, (byte) 0x9C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F,
(byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x98, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x20, (byte) 0x20, (byte) 0x9F,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,
(byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0x20,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
(byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,
(byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xF4, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0xF9, (byte) 0x20, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0x20, (byte) 0xFF,
};
public String getName()
{
return "windows-1256";
}
public String getLanguage()
{
return "ar";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_KOI8_R extends CharsetRecog_sbcs
{
private static final int[] ngrams = {
0x20C4CF, 0x20C920, 0x20CBC1, 0x20CBCF, 0x20CEC1, 0x20CEC5, 0x20CFC2, 0x20D0CF, 0x20D0D2, 0x20D2C1, 0x20D3CF, 0x20D3D4, 0x20D4CF, 0x20D720, 0x20D7CF, 0x20DAC1,
0x20DCD4, 0x20DED4, 0xC1CEC9, 0xC1D4D8, 0xC5CCD8, 0xC5CEC9, 0xC5D3D4, 0xC5D420, 0xC7CF20, 0xC920D0, 0xC9C520, 0xC9C920, 0xC9D120, 0xCCC5CE, 0xCCC920, 0xCCD8CE,
0xCEC120, 0xCEC520, 0xCEC9C5, 0xCEC9D1, 0xCECF20, 0xCECFD7, 0xCF20D0, 0xCF20D3, 0xCF20D7, 0xCFC7CF, 0xCFCA20, 0xCFCCD8, 0xCFCD20, 0xCFD3D4, 0xCFD720, 0xCFD7C1,
0xD0CFCC, 0xD0D2C5, 0xD0D2C9, 0xD0D2CF, 0xD2C1D7, 0xD2C5C4, 0xD3D120, 0xD3D4C1, 0xD3D4C9, 0xD3D4D7, 0xD4C5CC, 0xD4CF20, 0xD4CFD2, 0xD4D820, 0xD9C820, 0xDED4CF,
};
private static final byte[] byteMap = {
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,
(byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,
(byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,
(byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
(byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,
(byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
(byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,
(byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,
(byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,
(byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
};
public String getName()
{
return "KOI8-R";
}
public String getLanguage()
{
return "ru";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
abstract static class CharsetRecog_IBM424_he extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F */
/* 0- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 1- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 2- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 3- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 4- */ (byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 5- */ (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 6- */ (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 7- */ (byte) 0x40, (byte) 0x71, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x00, (byte) 0x40, (byte) 0x40,
/* 8- */ (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 9- */ (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* A- */ (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* B- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* C- */ (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* D- */ (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* E- */ (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* F- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
};
public String getLanguage()
{
return "he";
}
}
static class CharsetRecog_IBM424_he_rtl extends CharsetRecog_IBM424_he
{
public String getName()
{
return "IBM424_rtl";
}
private static final int[] ngrams = {
0x404146, 0x404148, 0x404151, 0x404171, 0x404251, 0x404256, 0x404541, 0x404546, 0x404551, 0x404556, 0x404562, 0x404569, 0x404571, 0x405441, 0x405445, 0x405641,
0x406254, 0x406954, 0x417140, 0x454041, 0x454042, 0x454045, 0x454054, 0x454056, 0x454069, 0x454641, 0x464140, 0x465540, 0x465740, 0x466840, 0x467140, 0x514045,
0x514540, 0x514671, 0x515155, 0x515540, 0x515740, 0x516840, 0x517140, 0x544041, 0x544045, 0x544140, 0x544540, 0x554041, 0x554042, 0x554045, 0x554054, 0x554056,
0x554069, 0x564540, 0x574045, 0x584540, 0x585140, 0x585155, 0x625440, 0x684045, 0x685155, 0x695440, 0x714041, 0x714042, 0x714045, 0x714054, 0x714056, 0x714069,
};
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap, (byte)0x40);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_IBM424_he_ltr extends CharsetRecog_IBM424_he
{
public String getName()
{
return "IBM424_ltr";
}
private static final int[] ngrams = {
0x404146, 0x404154, 0x404551, 0x404554, 0x404556, 0x404558, 0x405158, 0x405462, 0x405469, 0x405546, 0x405551, 0x405746, 0x405751, 0x406846, 0x406851, 0x407141,
0x407146, 0x407151, 0x414045, 0x414054, 0x414055, 0x414071, 0x414540, 0x414645, 0x415440, 0x415640, 0x424045, 0x424055, 0x424071, 0x454045, 0x454051, 0x454054,
0x454055, 0x454057, 0x454068, 0x454071, 0x455440, 0x464140, 0x464540, 0x484140, 0x514140, 0x514240, 0x514540, 0x544045, 0x544055, 0x544071, 0x546240, 0x546940,
0x555151, 0x555158, 0x555168, 0x564045, 0x564055, 0x564071, 0x564240, 0x564540, 0x624540, 0x694045, 0x694055, 0x694071, 0x694540, 0x714140, 0x714540, 0x714651
};
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap, (byte)0x40);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
abstract static class CharsetRecog_IBM420_ar extends CharsetRecog_sbcs
{
protected static byte[] byteMap = {
/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F */
/* 0- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 1- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 2- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 3- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 4- */ (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 5- */ (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x40, (byte) 0x40, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 6- */ (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 7- */ (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
/* 8- */ (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x8B, (byte) 0x8C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F,
/* 9- */ (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9B, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,
/* A- */ (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,
/* B- */ (byte) 0xB0, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x40, (byte) 0x40, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0xBD, (byte) 0xBE, (byte) 0xBF,
/* C- */ (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0xCB, (byte) 0x40, (byte) 0xCD, (byte) 0x40, (byte) 0xCF,
/* D- */ (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,
/* E- */ (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xEA, (byte) 0xEB, (byte) 0x40, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* F- */ (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x40,
};
public String getLanguage()
{
return "ar";
}
}
static class CharsetRecog_IBM420_ar_rtl extends CharsetRecog_IBM420_ar
{
private static final int[] ngrams = {
0x4056B1, 0x4056BD, 0x405856, 0x409AB1, 0x40ABDC, 0x40B1B1, 0x40BBBD, 0x40CF56, 0x564056, 0x564640, 0x566340, 0x567540, 0x56B140, 0x56B149, 0x56B156, 0x56B158,
0x56B163, 0x56B167, 0x56B169, 0x56B173, 0x56B178, 0x56B19A, 0x56B1AD, 0x56B1BB, 0x56B1CF, 0x56B1DC, 0x56BB40, 0x56BD40, 0x56BD63, 0x584056, 0x624056, 0x6240AB,
0x6240B1, 0x6240BB, 0x6240CF, 0x634056, 0x734056, 0x736240, 0x754056, 0x756240, 0x784056, 0x9A4056, 0x9AB1DA, 0xABDC40, 0xB14056, 0xB16240, 0xB1DA40, 0xB1DC40,
0xBB4056, 0xBB5640, 0xBB6240, 0xBBBD40, 0xBD4056, 0xBF4056, 0xBF5640, 0xCF56B1, 0xCFBD40, 0xDA4056, 0xDC4056, 0xDC40BB, 0xDC40CF, 0xDC6240, 0xDC7540, 0xDCBD40,
};
public String getName()
{
return "IBM420_rtl";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = matchIBM420(det, ngrams, byteMap, (byte)0x40);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_IBM420_ar_ltr extends CharsetRecog_IBM420_ar
{
private static final int[] ngrams = {
0x404656, 0x4056BB, 0x4056BF, 0x406273, 0x406275, 0x4062B1, 0x4062BB, 0x4062DC, 0x406356, 0x407556, 0x4075DC, 0x40B156, 0x40BB56, 0x40BD56, 0x40BDBB, 0x40BDCF,
0x40BDDC, 0x40DAB1, 0x40DCAB, 0x40DCB1, 0x49B156, 0x564056, 0x564058, 0x564062, 0x564063, 0x564073, 0x564075, 0x564078, 0x56409A, 0x5640B1, 0x5640BB, 0x5640BD,
0x5640BF, 0x5640DA, 0x5640DC, 0x565840, 0x56B156, 0x56CF40, 0x58B156, 0x63B156, 0x63BD56, 0x67B156, 0x69B156, 0x73B156, 0x78B156, 0x9AB156, 0xAB4062, 0xADB156,
0xB14062, 0xB15640, 0xB156CF, 0xB19A40, 0xB1B140, 0xBB4062, 0xBB40DC, 0xBBB156, 0xBD5640, 0xBDBB40, 0xCF4062, 0xCF40DC, 0xCFB156, 0xDAB19A, 0xDCAB40, 0xDCB156
};
public String getName()
{
return "IBM420_ltr";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = matchIBM420(det, ngrams, byteMap, (byte)0x40);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
static class CharsetRecog_cp866 extends CharsetRecog_sbcs
{
private static final int[] ngrams = {
// ' в ' ' во' ' до' ' за' ' и ' ' ка' ' ко' ' на' ' не' ' об' ' по' ' пр' ' ра' ' со' ' ст' ' то'
0x20A220, 0x20A2AE, 0x20A4AE, 0x20A7A0, 0x20A820, 0x20AAA0, 0x20AAAE, 0x20ADA0, 0x20ADA5, 0x20AEA1, 0x20AFAE, 0x20AFE0, 0x20E0A0, 0x20E1AE, 0x20E1E2, 0x20E2AE,
// ' чт' ' эт' 'ани' 'ать' 'го ' 'ель' 'ени' 'ест' 'ет ' 'и п' 'ие ' 'ии ' 'ия ' 'лен' 'ли ' 'льн'
0x20E7E2, 0x20EDE2, 0xA0ADA8, 0xA0E2EC, 0xA3AE20, 0xA5ABEC, 0xA5ADA8, 0xA5E1E2, 0xA5E220, 0xA820AF, 0xA8A520, 0xA8A820, 0xA8EF20, 0xABA5AD, 0xABA820, 0xABECAD,
// 'на ' 'не ' 'ние' 'ния' 'но ' 'нов' 'о в' 'о п' 'о с' 'ов ' 'ова' 'ого' 'ой ' 'оль' 'ом ' 'ост'
0xADA020, 0xADA520, 0xADA8A5, 0xADA8EF, 0xADAE20, 0xADAEA2, 0xAE20A2, 0xAE20AF, 0xAE20E1, 0xAEA220, 0xAEA2A0, 0xAEA3AE, 0xAEA920, 0xAEABEC, 0xAEAC20, 0xAEE1E2,
// 'пол' 'пре' 'при' 'про' 'рав' 'ред' 'ста' 'ств' 'сти' 'ся ' 'тел' 'то ' 'тор' 'ть ' 'что' 'ых '
0xAFAEAB, 0xAFE0A5, 0xAFE0A8, 0xAFE0AE, 0xE0A0A2, 0xE0A5A4, 0xE1E2A0, 0xE1E2A2, 0xE1E2A8, 0xE1EF20, 0xE2A5AB, 0xE2AE20, 0xE2AEE0, 0xE2EC20, 0xE7E2AE, 0xEBE520
};
private static final byte[] byteMap = {
/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F */
/* 0- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 1- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 2- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 3- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 4- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 5- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 6- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 7- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* 8- */ (byte) 0xA0, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,
/* 9- */ (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* A- */ (byte) 0xA0, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,
/* B- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* C- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* D- */ (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
/* E- */ (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,
/* F- */ (byte) 0xF0, (byte) 0xF0, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,
};
public String getName()
{
return "cp866";
}
public String getLanguage()
{
return "ru";
}
public CharsetMatch match(CharsetDetector det)
{
int confidence = match(det, ngrams, byteMap);
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
}
}
}
================================================
FILE: src/main/java/com/ibm/icu/text/CharsetRecognizer.java
================================================
/*
*******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
* Abstract class for recognizing a single charset.
* Part of the implementation of ICU's CharsetDetector.
* Each specific charset that can be recognized will have an instance of some subclass of this class.
* All interaction between the overall CharsetDetector and the stuff specific to an individual charset happens
* via the interface provided here.
* Instances of CharsetDetector DO NOT have or maintain state pertaining to a specific match or detect operation.
* The WILL be shared by multiple instances of CharsetDetector. They encapsulate const charset-specific information.
*/
abstract class CharsetRecognizer {
/**
* Get the IANA name of this charset.
* @return the charset name.
*/
abstract String getName();
/**
* Get the ISO language code for this charset.
* @return the language code, or null if the language cannot be determined.
*/
public String getLanguage()
{
return null;
}
/**
* Test the match of this charset with the input text data
* which is obtained via the CharsetDetector object.
*
* @param det The CharsetDetector, which contains the input text
* to be checked for being in this charset.
* @return A CharsetMatch object containing details of match
* with this charset, or null if there was no match.
*/
abstract CharsetMatch match(CharsetDetector det);
}
================================================
FILE: src/main/java/com/mucommander/PlatformManager.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
* ~/Library/Preferences/trolCommander/ under MAC OS X.~/.trolcommander/ under all other OSes.
*
* ~/Library/Preferences/trolCommander/ under MAC OS X.~/.trolcommander/ under all other OSes.folder is a file, its parent folder will be used instead. If it doesn't exist,
* this method will create it.
*
* @param folder path to the folder in which trolCommander will look for its preferences.
* @throws IOException if an IO error occurs.
* @see #getPreferencesFolder()
* @see #setPreferencesFolder(String)
* @see #setPreferencesFolder(AbstractFile)
*/
public static void setPreferencesFolder(File folder) throws IOException {
AbstractFile file = FileFactory.getFile(folder.getAbsolutePath());
if (file != null) {
setPreferencesFolder(file);
}
}
/**
* Sets the path to the folder in which trolCommander will look for its preferences.
* folder is a file, its parent folder will be used instead. If it doesn't exist,
* this method will create it.
*
* @param path path to the folder in which trolCommander will look for its preferences.
* @throws IOException if an IO error occurs.
* @see #getPreferencesFolder()
* @see #setPreferencesFolder(File)
* @see #setPreferencesFolder(AbstractFile)
*/
public static void setPreferencesFolder(String path) throws IOException {
AbstractFile folder = FileFactory.getFile(path);
if (folder == null) {
setPreferencesFolder(new File(path));
} else {
setPreferencesFolder(folder);
}
}
/**
* Sets the path to the folder in which trolCommander will look for its preferences.
* folder is a file, its parent folder will be used instead. If it doesn't exist,
* this method will create it.
*
* @param folder path to the folder in which trolCommander will look for its preferences.
* @throws IOException if an IO error occurs.
* @see #getPreferencesFolder()
* @see #setPreferencesFolder(String)
* @see #setPreferencesFolder(File)
*/
public static void setPreferencesFolder(AbstractFile folder) throws IOException {
if (!folder.exists()) {
folder.mkdir();
} else if (!folder.isBrowsable()) {
folder = folder.getParent();
}
prefFolder = folder;
}
}
================================================
FILE: src/main/java/com/mucommander/RuntimeConstants.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2020 Oleg Trifonov
*
* trolCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* trolCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see MAJOR.MINOR.DEV). */
public static final String VERSION;
/** Date at which the build was generated (YYYYMMDD). */
public static final String BUILD_DATE;
/** Time at which the build was generated (HHMM). */
public static final String BUILD_TIME;
/** Copyright information (YYYY-YYYY). */
public static final String COPYRIGHT;
/** String describing the software (trolCommander vMAJOR.MINOR.DEV). */
public static final String APP_STRING;
/** String describing the trolCommander build number. */
public static final String BUILD_NUMBER;
/** YYYYMMDDHHmm */
public static final String BUILD_CODE;
static {
Attributes attributes = getManifestAttributes();
if (attributes == null) { // No MANIFEST.MF found, use default values.
VERSION = "?";
COPYRIGHT = "2013-" + Calendar.getInstance().get(Calendar.YEAR);
// We use a date that we are sure is later than the latest version to trigger the version checker.
// After all, the JAR appears to be corrupt and should be upgraded.
BUILD_DATE = DEFAULT_RELEASE_DATE;
BUILD_TIME = null;
VERSION_URL = DEFAULT_VERSION_URL;
BUILD_NUMBER = "?";
BUILD_CODE = null;
} else { // A MANIFEST.MF file was found, extract data from it.
VERSION = getAttribute(attributes, "Specification-Version");
BUILD_DATE = getAttribute(attributes, "Build-Date");
BUILD_TIME = getAttribute(attributes, "Build-Time");
VERSION_URL = getAttribute(attributes, "Build-URL");
BUILD_NUMBER = getAttribute(attributes, "Implementation-Version");
// Protection against corrupt manifest files.
COPYRIGHT = BUILD_DATE.length() > 4 ? BUILD_DATE.substring(0, 4) : DEFAULT_RELEASE_DATE;
BUILD_CODE = BUILD_DATE + BUILD_TIME;
}
APP_STRING = "trolCommander v" + VERSION;
}
@Nullable
private static Attributes getManifestAttributes() {
try (InputStream in = ResourceLoader.getResourceAsStream("META-INF/MANIFEST.MF", ResourceLoader.getDefaultClassLoader(), ResourceLoader.getRootPackageAsFile(RuntimeConstants.class))) {
if (in != null) {
Manifest manifest = new Manifest();
manifest.read(in);
return manifest.getMainAttributes();
}
} catch (IOException e) {
LOGGER.warn("Failed to read MANIFEST.MF, default values will be used", e);
}
return null;
}
/**
* Extract the requested attribute value.
* @param attributes attributes from which to extract the requested value.
* @param name name of the attribute to retrieve.
* @return the requested attribute value.
*/
private static String getAttribute(Attributes attributes, String name) {
String buffer = attributes.getValue(name);
return buffer == null ? "?" : buffer;
}
private static boolean is4KDisplay() {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
return screenSize.width*screenSize.height > 3500*3500;
}
}
================================================
FILE: src/main/java/com/mucommander/StressTester.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see Launcher.
*/
private TrolCommander() {}
/**
* This method can be called to wait until the application has been launched. The caller thread will be blocked
* until the application has been launched.
* This method will return immediately if the application has already been launched when it is called.
*/
public static void waitUntilLaunched() {
getLogger().debug("called, thread {}", Thread.currentThread());
synchronized(LAUNCH_LOCK) {
while (isLaunching) {
try {
getLogger().debug("waiting");
LAUNCH_LOCK.wait();
} catch (InterruptedException e) {
// will loop
}
}
}
}
// - Boot code --------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Method used to migrate commands that used to be defined in the configuration but were moved to commands.xml.
* @param useName name of the use custom command configuration variable.
* @param commandName name of the custom command configuration variable.
*/
public static void migrateCommand(String useName, String commandName, String alias) {
String command;
if (TcConfigurations.getPreferences().getBooleanVariable(useName) && (command = TcConfigurations.getPreferences().getVariable(commandName)) != null) {
CommandManager.registerCommand(new Command(alias, command, CommandType.SYSTEM_COMMAND));
TcConfigurations.getPreferences().removeVariable(useName);
TcConfigurations.getPreferences().removeVariable(commandName);
}
}
/**
* Main method used to startup muCommander.
* @param args command line arguments.
*/
public static void main(String[] args) {
if (OsFamily.MAC_OS_X.isCurrent()) {
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "trolCommander");
// disable openGL in javaFX (used for HtmlViewer) as it cashes JVM under vmWare
System.setProperty("prism.order", "sw");
}
Profiler.start("init");
Profiler.start("loading");
int processors = Runtime.getRuntime().availableProcessors();
System.out.println("Current OS family: " + OsFamily.getCurrent());
System.out.println("Processors: " + processors);
try (LauncherExecutor executor = new LauncherExecutor(processors <= 0 ? 1 : processors)) {
LauncherCmdHelper helper = new LauncherCmdHelper(args, true, false);
// Whether, or not to ignore warnings when booting.
helper.parseArgs();
ListAndroidMenu.
*/
public AndroidMenu() {
super(Translator.get("adb.android_devices"));
setIcon(IconManager.getIcon(IconManager.IconSet.FILE, "android.png"));
// Menu items will be added when menu gets selected
addMenuListener(this);
}
/**
* Returns the action to perform for the given item.
*
* @param deviceSerial the serial number of the device
* @return the action to perform for the given Android device
*/
public abstract TcAction getMenuItemAction(String deviceSerial);
@Override
public void menuSelected(MenuEvent e) {
// Remove previous menu items (if any)
removeAll();
List
*
*
* @author Maxence Bernard
*/
public class CredentialsManager {
private static Logger logger;
/** Contains volatile CredentialsMapping instances, lost when the application terminates */
private static final Listpath is not available.
*/
public static void setCredentialsFile(String path) throws FileNotFoundException {
AbstractFile file = FileFactory.getFile(path);
if (file == null) {
setCredentialsFile(new File(path));
} else {
setCredentialsFile(file);
}
}
/**
* Sets the path to the credentials file.
*
* @param file path to the credentials file
* @throws FileNotFoundException if path is not available.
*/
public static void setCredentialsFile(File file) throws FileNotFoundException {
setCredentialsFile(FileFactory.getFile(file.getAbsolutePath()));
}
/**
* Sets the path to the credentials file.
*
* @param file path to the credentials file
* @throws FileNotFoundException if path is not available.
*/
public static void setCredentialsFile(AbstractFile file) throws FileNotFoundException {
if (file.isBrowsable()) {
throw new FileNotFoundException("Not a valid file: " + file);
}
credentialsFile = file;
}
/**
* Tries to load credentials from the credentials file if it exists. Does nothing if it doesn't.
*
* @throws Exception if an error occurs while loading the credentials file.
*/
public static void loadCredentials() throws Exception {
AbstractFile credentialsFile = getCredentialsFile();
if (credentialsFile.exists()) {
getLogger().debug("Found credentials file: "+credentialsFile.getAbsolutePath());
// Parse the credentials file
new CredentialsParser().parse(credentialsFile);
getLogger().debug("Credentials file loaded.");
} else {
getLogger().debug("No credentials file found at " + credentialsFile.getAbsolutePath());
}
}
/**
* Tries to write the credentials file. Unless the 'forceWrite' is set to true, the credentials file will be written
* only if changes were made to persistent entries since last write.
*
* @param forceWrite if false, the credentials file will be written only if changes were made to persistent entries
* since last write, if true the file will always be written.
* @throws IOException if an I/O error occurs.
*/
public static void writeCredentials(boolean forceWrite) throws IOException {
// Write credentials file only if changes were made to persistent entries since last write, or if write is forced
if (!(forceWrite || saveNeeded)) {
return;
}
BackupOutputStream out = null;
try {
credentialsFile = getCredentialsFile();
CredentialsWriter.write(out = new BackupOutputStream(credentialsFile));
saveNeeded = false;
} finally {
if (out != null) {
// TODO autoclosable
try {
out.close();
} catch(Exception ignore) {}
}
}
// Under UNIX-based systems, change the credentials file's permissions so that the file can't be read by
// 'group' and 'other'.
boolean fileSecured = !OsFamily.getCurrent().isUnixBased() || Chmod.chmod(credentialsFile, 0600); // rw-------
if (fileSecured) {
getLogger().debug("Credentials file saved successfully.");
} else {
getLogger().warn("Credentials file could not be chmod!");
}
}
/**
* Returns an array of {@link CredentialsMapping} that match the location designated by the given {@link FileURL}
* and which can be used to authenticate. The location is compared against all known credentials, both volatile and
* persistent.
*
* CredentialsMapping to authenticate the
* given {@link FileURL}.
* FileURL will be lost and replaced with the new ones.
* If properties with the same key are defined both in the realm and the given FileURL, the ones from the FileURL
* will be preserved.
*
* @param location the FileURL to authenticate
* @param credentialsMapping the credentials to use to authenticate the given FileURL
*/
public static void authenticate(FileURL location, CredentialsMapping credentialsMapping) {
location.setCredentials(credentialsMapping.getCredentials());
FileURL realm = credentialsMapping.getRealm();
Settrue if a set of credentials was found and used to authenticate the URL, false
* otherwise.
*
* Vector, preserving its position. If the
* vector contains no such object, it is added to the end of the vector.
*
* @param list the List to replace/add the object to
* @param o the object to replace/add
*/
private static void replaceListElement(Listtrue if these credentials should be saved when the application terminates.
*
* @return true if these credentials should be saved when the application terminates, false otherwise.
*/
public boolean isPersistent() {
return isPersistent;
}
/**
* Returns true if the given Object is a {@link com.mucommander.auth.CredentialsMapping} instance
* whose credentials and realm are equals to those of this instance.
*
* @param o the Object to test for equality
* @return true if both CredentialsMapping instances are equal
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof CredentialsMapping cm)) { // Note: CredentialsMapping is final, no need to test classes
return false;
}
return cm.credentials.equals(this.credentials, false) && cm.realm.equals(this.realm, false, true);
}
@Override
public String toString() {
return credentials.toString()+" "+realm.toString(false);
}
}
================================================
FILE: src/main/java/com/mucommander/auth/CredentialsParser.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see null if it is unknown.
* null if it is unknown.
*/
public String getVersion() {
return version;
}
///////////////////////////////////
// ContentHandler implementation //
///////////////////////////////////
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
characters.setLength(0);
switch (qName) {
case ELEMENT_CREDENTIALS:
// Reset parsing variables
url = null;
urlProperties = null;
login = null;
password = null;
break;
// Property element (properties will be set when credentials element ends
case ELEMENT_PROPERTY:
if (urlProperties == null)
urlProperties = new Hashtable<>();
urlProperties.put(attributes.getValue(ATTRIBUTE_NAME), attributes.getValue(ATTRIBUTE_VALUE));
break;
// Root element, the 'encryption' attribute specifies which encoding was used to encrypt passwords
case ELEMENT_ROOT:
encryptionMethod = attributes.getValue("encryption");
version = attributes.getValue(ATTRIBUTE_VERSION);
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case ELEMENT_CREDENTIALS:
if (url == null || login == null || password == null) {
getLogger().info("Missing value, credentials ignored: url=" + url + " login=" + login);
return;
}
// Copy properties into FileURL instance (if any)
if (urlProperties != null) {
for (String key : urlProperties.keySet())
url.setProperty(key, urlProperties.get(key));
}
// Decrypt password
try {
password = XORCipher.decryptXORBase64(password);
} catch (IOException e) {
getLogger().info("Password could not be decrypted: " + password + ", credentials will be ignored");
return;
}
// Add credentials to persistent credentials list
CredentialsManager.getPersistentCredentialMappings().add(new CredentialsMapping(new Credentials(login, password), url, true));
break;
case ELEMENT_URL:
try {
url = FileURL.getFileURL(characters.toString().trim());
} catch (MalformedURLException e) {
getLogger().info("Malformed URL: " + characters + ", location will be ignored");
}
break;
case ELEMENT_LOGIN:
login = characters.toString().trim();
break;
case ELEMENT_PASSWORD:
password = characters.toString().trim();
break;
}
}
@Override
public void characters(char[] ch, int offset, int length) {
characters.append(ch, offset, length);
}
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(CredentialsParser.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/auth/CredentialsWriter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see true if Bonjour services discovery is currently running.
* @return true if Bonjour services discovery is currently running, false otherwise.
*/
public static boolean isActive() {
return jmDNS != null;
}
/**
* Returns all currently available Bonjour services. The returned array may be empty but never null.
* If BonjourDirectory is not currently active ({@link #isActive()}, an empty array will be returned.
* @return all currently available Bonjour services
*/
public static BonjourService[] getServices() {
BonjourService[] servicesArray = new BonjourService[services.size()];
services.toArray(servicesArray);
return servicesArray;
}
/**
* Wraps a Bonjour service into a {@link BonjourService} object and returns it. Returns null if
* the service type doesn't correspond to any of the supported protocols, or if the service URL is malformed.
*
* @param serviceInfo the ServiceInfo to wrap into a BonjourService
* @return a BonjourService instance corresponding to the given ServiceInfo
*/
private static BonjourService createBonjourService(ServiceInfo serviceInfo) {
try {
String type = serviceInfo.getType();
// Looks for the file protocol corresponding to the service type
for (String[] KNOWN_SERVICE_TYPE : KNOWN_SERVICE_TYPES) {
if (KNOWN_SERVICE_TYPE[0].equals(type)) {
return new BonjourService(serviceInfo.getName(), FileURL.getFileURL(serviceInfo.getURL(KNOWN_SERVICE_TYPE[1])), serviceInfo.getQualifiedName());
}
}
} catch (MalformedURLException e) {
// Null will be returned
}
return null;
}
////////////////////////////////////
// ServiceListener implementation //
////////////////////////////////////
public void serviceAdded(final ServiceEvent serviceEvent) {
getLogger().trace("name="+serviceEvent.getName()+" type="+serviceEvent.getType());
// Ignore if Bonjour has been disabled
if (!isActive()) {
return;
}
// Resolve service info in a separate thread, serviceResolved() will be called once service info has been resolved.
// Not spawning a thread often leads to service info loss (serviceResolved() not called).
new Thread(() -> jmDNS.requestServiceInfo(serviceEvent.getType(), serviceEvent.getName(), SERVICE_RESOLUTION_TIMEOUT)).start();
}
public void serviceResolved(ServiceEvent serviceEvent) {
getLogger().trace("name="+serviceEvent.getName()+" type="+serviceEvent.getType()+" info="+serviceEvent.getInfo());
// Ignore if Bonjour has been disabled
if (!isActive())
return;
// Creates a new BonjourService corresponding to the new service and add it to the list of current Bonjour services
ServiceInfo serviceInfo = serviceEvent.getInfo();
if(serviceInfo!=null) {
if(serviceInfo.getInetAddress() instanceof Inet6Address) {
// IPv6 addresses not supported at this time + they seem not to be correctly handled by ServiceInfo
getLogger().debug("ignoring IPv6 service");
return;
}
BonjourService bs = createBonjourService(serviceInfo);
// Synchronized to properly handle duplicate calls
synchronized(instance) {
if(bs!=null && !services.contains(bs)) {
getLogger().debug("BonjourService "+bs+" added");
services.add(bs);
}
}
}
}
public void serviceRemoved(ServiceEvent serviceEvent) {
getLogger().trace("name="+serviceEvent.getName()+" type="+serviceEvent.getType());
// Ignore if Bonjour has been disabled
if(!isActive())
return;
// Looks for an existing BonjourService instance corresponding to the service being removed and removes it from
// the list of current Bonjour services.
// ServiceInfo should be available in JmDNS's cache.
ServiceInfo serviceInfo = jmDNS.getServiceInfo(serviceEvent.getType(), serviceEvent.getName());
if (serviceInfo != null) {
if(serviceInfo.getInetAddress() instanceof Inet6Address) {
// IPv6 addresses not supported at this time + they seem not to be correctly handled by ServiceInfo
getLogger().debug("ignoring IPv6 service");
return;
}
BonjourService bs = createBonjourService(serviceInfo);
// Synchronized to properly handle duplicate calls
synchronized(instance) {
// Note: BonjourService#equals() uses the service's fully qualified name as the discriminator.
if (bs != null && services.contains(bs)) {
getLogger().debug("BonjourService "+bs+" removed");
services.remove(bs);
}
}
}
}
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(BonjourDirectory.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/bonjour/BonjourMenu.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see BonjourMenu.
*/
public BonjourMenu() {
super(Translator.get("bonjour.bonjour_services"));
setIcon(IconManager.getIcon(IconManager.IconSet.FILE, "bonjour.png"));
// Menu items will be added when menu gets selected
addMenuListener(this);
}
/**
* Returns the action to perform for the given {@link BonjourService}. This method is called for every
* BonjourService available when this menu is selected.
*
* @param bs the BonjourService
* @return the action to perform for the given BonjourService
*/
public abstract TcAction getMenuItemAction(BonjourService bs);
/////////////////////////////////
// MenuListener implementation //
/////////////////////////////////
@Override
public void menuSelected(MenuEvent menuEvent) {
// Remove previous menu items (if any)
removeAll();
if (!BonjourDirectory.isActive()) {
// Inform that Bonjour support has been disabled
add(new JMenuItem(Translator.get("bonjour.bonjour_disabled"))).setEnabled(false);
return;
}
BonjourService[] services = BonjourDirectory.getServices();
if (services.length > 0) {
// Add a menu item for each Bonjour service.
// When clicked, the corresponding URL will be opened in the active table.
MnemonicHelper mnemonicHelper = new MnemonicHelper();
for (BonjourService service : services) {
JMenuItem menuItem = new JMenuItem(getMenuItemAction(service));
menuItem.setMnemonic(mnemonicHelper.getMnemonic(menuItem.getText()));
add(menuItem);
}
} else {
// Inform that no service have been discovered
add(new JMenuItem(Translator.get("bonjour.no_service_discovered"))).setEnabled(false);
}
}
@Override
public void menuDeselected(MenuEvent menuEvent) {
}
@Override
public void menuCanceled(MenuEvent menuEvent) {
}
}
================================================
FILE: src/main/java/com/mucommander/bonjour/BonjourService.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see true if the given Object is a BonjourService instance with the same fully qualified name.
*/
@Override
public boolean equals(Object o) {
return o instanceof BonjourService && fullyQualifiedName.equals(((BonjourService) o).fullyQualifiedName);
}
/**
* Returns a String representation of this BonjourService in the form name / url.
*/
@Override
public String toString() {
return name + " / " + url.toString(false);
}
}
================================================
FILE: src/main/java/com/mucommander/bonjour/package.html
================================================
*
*
* @author Maxence Bernard
*/
public class Bookmark implements Cloneable {
private String name;
private String location;
private String parent;
/**
* Creates a new Bookmark using the given name and location.
*
* @param name name given to this bookmark
* @param location location (path or URL) this bookmark points to
*/
public Bookmark(String name, String location, String parent) {
// Use setters to checks for null values
setName(name);
setLocation(location);
setParent(parent);
}
/**
* Returns this bookmark's name.
* @return this bookmark's name.
* @see #setName(String)
*/
public String getName() {
return name;
}
/**
* Changes this bookmark's name to the given one and fires an event to registered {@link BookmarkListener}
* instances.
* @param newName bookmark's new name.
* @see #getName()
*/
public void setName(String newName) {
if (newName == null) {
newName = "";
}
if (!newName.equals(this.name)) {
this.name = newName;
// Notify registered listeners of the change
BookmarkManager.fireBookmarksChanged();
}
}
/**
* Returns this bookmark's location which should normally designate a path or file URL, but which isn't
* necessarily valid nor exists.
* @return this bookmark's location.
* @see #setLocation(String)
*/
public String getLocation() {
return location;
}
/**
* Changes this bookmark's location to the given one and fires an event to registered {@link BookmarkListener}
* instances.
* @param newLocation bookmark's new location.
* @see #getLocation()
*/
public void setLocation(String newLocation) {
// Replace null values by empty strings
if (newLocation == null) {
newLocation = "";
}
if (!newLocation.equals(this.location)) {
this.location = newLocation;
// Notify registered listeners of the change
BookmarkManager.fireBookmarksChanged();
}
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
if (parent != null && parent.trim().isEmpty()) {
parent = null;
}
this.parent = parent;
}
/**
* Returns a clone of this bookmark.
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Returns the bookmark's name.
*/
public String toString() {
if (parent != null) {
return parent + " -> " + name;
}
return name;
}
public boolean equals(Object object) {
if (!(object instanceof Bookmark bookmark)) {
return false;
}
return bookmark.getName().equals(name);
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/BookmarkBuilder.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see BookmarkManager.
*/
private BookmarkManager() {}
// - Bookmark building -----------------------------------------------------
// -------------------------------------------------------------------------
/**
* Passes messages about all known bookmarks to the specified builder.
* @param builder where to send bookmark building messages.
* @throws BookmarkException if an error occurs.
*/
private static synchronized void buildBookmarks(BookmarkBuilder builder) throws BookmarkException {
builder.startBookmarks();
for (Bookmark bookmark : bookmarks) {
builder.addBookmark(bookmark.getName(), bookmark.getLocation(), bookmark.getParent());
}
builder.endBookmarks();
}
// - Bookmark file access --------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the path to the bookmark file.
* setBookmarksFile(FileFactory.getFile(file)).
*
* @param path path to the bookmarks file
* @exception FileNotFoundException if path is not accessible.
* @see #getBookmarksFile()
*/
public static void setBookmarksFile(String path) throws FileNotFoundException {
AbstractFile file = FileFactory.getFile(path);
if (file == null) {
setBookmarksFile(new File(path));
} else {
setBookmarksFile(file);
}
}
/**
* Sets the path to the bookmarks file.
* setBookmarksFile(FileFactory.getFile(file.getAbsolutePath())).
*
* @param file path to the bookmarks file
* @exception FileNotFoundException if path is not accessible.
* @see #getBookmarksFile()
*/
private static void setBookmarksFile(File file) throws FileNotFoundException {
setBookmarksFile(FileFactory.getFile(file.getAbsolutePath()));
}
/**
* Sets the path to the bookmarks file.
* @param file path to the bookmarks file
* @exception FileNotFoundException if path is not accessible.
* @see #getBookmarksFile()
*/
private static synchronized void setBookmarksFile(AbstractFile file) throws FileNotFoundException {
if (file.isBrowsable()) {
throw new FileNotFoundException("Not a valid file: " + file.getAbsolutePath());
}
bookmarksFile = file;
}
// - Bookmarks loading -----------------------------------------------------
// -------------------------------------------------------------------------
/**
* Loads all available bookmarks.
* @throws Exception if an error occurs.
*/
public static synchronized void loadBookmarks() throws Exception {
// Parse the bookmarks file
isLoading = true;
try (InputStream in = new BackupInputStream(getBookmarksFile())) {
readBookmarks(in, new Loader());
isLoading = false;
} catch (IOException e) {
isLoading = false;
throw e;
}
}
/**
* Reads bookmarks from the specified InputStream.
* @param in where to read bookmarks from.
* @throws Exception if an error occurs.
*/
public static void readBookmarks(InputStream in) throws Exception {
readBookmarks(in, new Loader());
}
/**
* Reads bookmarks from the specified InputStream and passes messages to the specified {@link BookmarkBuilder}.
* @param in where to read bookmarks from.
* @param builder where to send builing messages to.
* @throws Exception if an error occurs.
*/
public static synchronized void readBookmarks(InputStream in, BookmarkBuilder builder) throws Exception {
new BookmarkParser().parse(in, builder);
}
// - Bookmarks writing -----------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns a {@link BookmarkBuilder} that will write all building messages as XML to the specified output stream.
* @param out where to write the bookmarks' XML content.
* @return a {@link BookmarkBuilder} that will write all building messages as XML to the specified output stream.
* @throws IOException if an IO related error occurs.
*/
public static BookmarkBuilder getBookmarkWriter(OutputStream out) throws IOException {
return new BookmarkWriter(out);
}
/**
* Writes all known bookmarks to the bookmark {@link #getBookmarksFile() file}.
* @param forceWrite if false, the bookmarks file will be written only if changes were made to bookmarks since
* last write, if true the file will always be written
* @throws IOException if an I/O error occurs.
* @throws BookmarkException if an error occurs.
*/
public static synchronized void writeBookmarks(boolean forceWrite) throws IOException, BookmarkException {
// Write bookmarks file only if changes were made to the bookmarks since last write, or if write is forced.
if (!forceWrite && !saveNeeded) {
return;
}
try (OutputStream out = new BackupOutputStream(getBookmarksFile())) {
buildBookmarks(getBookmarkWriter(out));
saveNeeded = false;
}
}
// - Bookmarks access ------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns an {@link AlteredVector} that contains all bookmarks.
*
*
*
*/
static void fireBookmarksChanged() {
// Bookmarks file will need to be saved
if (!isLoading) {
saveNeeded = true;
}
lastBookmarkChangeTime = System.currentTimeMillis();
// Do not fire event if events are currently disabled
if (!fireEvents) {
return;
}
synchronized(listeners) {
// Iterate on all listeners
for (BookmarkListener listener : listeners.keySet()) {
listener.bookmarksChanged();
}
}
}
/**
* Specifies whether bookmark events should be fired when a change in the bookmarks is detected. This allows
* to temporarily suspend events firing when a lot of them are made, for example when editing the bookmarks list.
*
* null if it is unknown.
* null if it is unknown.
*/
public String getVersion() {
return version;
}
/* ------------------------ */
/* ContentHandler methods */
/* ------------------------ */
@Override
public void startDocument() throws SAXException {
try {
builder.startBookmarks();
} catch (BookmarkException e) {
throw new SAXException(e);
}
}
@Override
public void endDocument() throws SAXException {
try {
builder.endBookmarks();
} catch (BookmarkException e) {
throw new SAXException(e);
}
}
/**
* Method called when some PCDATA has been found in an XML node.
*/
@Override
public void characters(char[] ch, int start, int length) {
characters.append(ch, start, length);
}
/**
* Notifies the parser that a new XML node has been found.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
characters.setLength(0);
if (qName.equals(ELEMENT_ROOT)) {
version = attributes.getValue(ATTRIBUTE_VERSION);
} else if (qName.equals(ELEMENT_BOOKMARK)) {
// Reset parsing variables
bookmarkName = null;
bookmarkLocation = null;
bookmarkParent = null;
}
}
/**
* Notifies the parser that an XML node has been closed.
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
case ELEMENT_BOOKMARK:
addBookmark();
break;
case ELEMENT_NAME:
bookmarkName = characters.toString().trim();
break;
case ELEMENT_LOCATION:
bookmarkLocation = characters.toString().trim();
break;
case ELEMENT_PARENT:
bookmarkParent = characters.toString().trim();
break;
// Note: url element has been deprecated in 0.8 beta3 but is still checked against for upward compatibility.
// case ELEMENT_URL:
// // Until early 0.8 beta3 nightly builds, credentials were stored directly in the bookmark's url.
// // Now bookmark locations are free of credentials, these are stored in a dedicated credentials file where
// // the password is encrypted.
// try {
// FileURL url = FileURL.getFileURL(characters.toString().trim());
// Credentials credentials = url.getCredentials();
//
// // If the URL contains credentials, import them into CredentialsManager and remove credentials
// // from the bookmark's location
// if (credentials != null) {
// CredentialsManager.addCredentials(new CredentialsMapping(credentials, url, true));
// bookmarkLocation = url.toString(false);
// } else {
// bookmarkLocation = characters.toString().trim();
// }
// } catch (MalformedURLException e) {
// bookmarkLocation = characters.toString().trim();
// }
// break;
}
}
private void addBookmark() throws SAXException {
if (bookmarkName == null || bookmarkLocation == null) {
getLogger().info("Missing value, bookmark ignored: name=" + bookmarkName + " location=" + bookmarkLocation);
return;
}
try {
builder.addBookmark(bookmarkName, bookmarkLocation, bookmarkParent);
} catch (BookmarkException e) {
throw new SAXException(e);
}
}
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(BookmarkParser.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/BookmarkWriter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see bookmark:// file system.
* @author Nicolas Rinaudo
*/
public class BookmarkFile extends ProtocolFile {
// - Instance fields -------------------------------------------------------
// -------------------------------------------------------------------------
/** Bookmark wrapped by this abstract file. */
private final Bookmark bookmark;
/** Underlying abstract file. */
private AbstractFile file;
/** Permissions for all bookmark files: rw- (600 octal). Only the 'user' permissions bits are supported. */
final static FilePermissions PERMISSIONS = new SimpleFilePermissions(384, 448);
/**
* Creates a new bookmark file wrapping the specified bookmark.
* @param bookmark bookmark to wrap.
* @throws IOException if the specified bookmark's URL cannot be resolved.
*/
BookmarkFile(Bookmark bookmark) throws IOException {
super(FileURL.getFileURL(BookmarkProtocolProvider.BOOKMARK + "://" + java.net.URLEncoder.encode(bookmark.getName(), StandardCharsets.UTF_8)));
this.bookmark = bookmark;
}
/**
* Returns the AbstractFile this instance wraps.
* AbstractFile this instance wraps.
*/
private synchronized AbstractFile getUnderlyingFile() {
// Resolves the file if necessary.
if (file == null) {
file = FileFactory.getFile(bookmark.getLocation());
}
return file;
}
/**
* Returns the underlying bookmark.
* @return the underlying bookmark.
*/
public Bookmark getBookmark() {
return bookmark;
}
// - AbstractFile methods --------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the underlying bookmark's name.
* @return the underlying bookmark's name.
*/
@Override
public String getName() {
return bookmark.getName();
}
/**
* Returns the wrapped file's descendants.
* @return the wrapped file's descendants.
* @throws IOException if an I/O error occurs.
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
@Override
public AbstractFile[] ls() throws IOException, UnsupportedFileOperationException {
return getUnderlyingFile().ls();
}
/**
* Returns the wrapped file's parent.
* @return the wrapped file's parent.
* @see #setParent(AbstractFile)
*/
@Override
public AbstractFile getParent() {
try {
return new BookmarkRoot();
} catch(IOException e) {
return null;
}
}
/**
* Returns the result of the wrapped file's getFreeSpace() methods.
* @return the result of the wrapped file's getFreeSpace() methods.
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
@Override
public long getFreeSpace() throws IOException, UnsupportedFileOperationException {
return getUnderlyingFile().getFreeSpace();
}
/**
* Returns the result of the wrapped file's getTotalSpace() methods.
* @return the result of the wrapped file's getTotalSpace() methods.
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
@Override
public long getTotalSpace() throws IOException, UnsupportedFileOperationException {
return getUnderlyingFile().getTotalSpace();
}
/**
* Returns false.
* @return false.
*/
@Override
public boolean isDirectory() {
return true;
}
/**
* Sets the wrapped file's parent.
* @param parent object to use as the wrapped file's parent.
* @see AbstractFile#getParent()
*/
@Override
public void setParent(AbstractFile parent) {
getUnderlyingFile().setParent(parent);}
/**
* Returns true if the specified bookmark exists.
* true if the specified bookmark exists, false otherwise.
*/
@Override
public boolean exists() {
return BookmarkManager.getBookmark(bookmark.getName()) != null;
}
@Override
public void mkfile() {
BookmarkManager.addBookmark(bookmark);
}
public boolean equals(Object o) {
// Makes sure we're working with an abstract file.
if (!(o instanceof AbstractFile)) {
return false;
}
// Retrieves the actual file instance.
// We might have received a Proxied or Cached file, so we need to make sure
// we 'unwrap' that before comparing.
AbstractFile file = ((AbstractFile)o).getAncestor();
// We only know how to compare one bookmark file to the other.
if (file instanceof BookmarkFile) {
return bookmark.equals(((BookmarkFile)file).getBookmark());
}
return false;
}
@Override
public String getCanonicalPath() {return bookmark.getLocation();}
// - Bookmark renaming -----------------------------------------------------
// -------------------------------------------------------------------------
/**
* Attempts to rename the bookmark to the specified destination.
* The operation will only be carried out if the specified destination is a BookmarkFile or has an
* ancestor that is.
*
* @param destination where to move the bookmark to.
* @throws IOException if the operation could not be carried out.
*/
@Override
public void renameTo(AbstractFile destination) throws IOException {
checkRenamePrerequisites(destination, true, true);
destination = destination.getTopAncestor();
// Makes sure we're working with a bookmark.
if (!(destination instanceof BookmarkFile)) {
throw new IOException();
}
// Creates the new bookmark and checks for conflicts.
Bookmark newBookmark = new Bookmark(destination.getName(), bookmark.getLocation(), bookmark.getParent());
Bookmark oldBookmark = BookmarkManager.getBookmark(newBookmark.getName());
if (oldBookmark != null) {
BookmarkManager.removeBookmark(oldBookmark);
}
// Adds the new bookmark and deletes its 'old' version.
BookmarkManager.addBookmark(newBookmark);
BookmarkManager.removeBookmark(bookmark);
}
// TODO: bookmark deleting is currently disabled as a quick fix for #329
// /**
// * Deletes the bookmark.
// * BookmarkFile,
* this will duplicate the bookmark. Otherwise, this method will fail.
*
* @param destination where to copy the bookmark to.
* @throws FileTransferException if the specified destination is not an instance of BookmarkFile.
*/
@Override
public void copyRemotelyTo(AbstractFile destination) throws IOException {
// Makes sure we're working with a bookmark.
destination = destination.getTopAncestor();
if (!(destination instanceof BookmarkFile)) {
throw new IOException();
}
// Copies this bookmark to the specified destination.
BookmarkManager.addBookmark(new Bookmark(destination.getName(), bookmark.getLocation(), bookmark.getParent()));
}
// - Permissions -----------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the same permissions for all boookmark files: rw- (600 octal).
* Only the 'user' permissions bits are supported.
* @return this file's permissions.
* @see #changePermission(int,int,boolean)
*/
@Override
public FilePermissions getPermissions() {
return PERMISSIONS;
}
/**
* Always throws an {@link UnsupportedFileOperationException} when called: bookmarks always have all permissions,
* this is not changeable.
*
* @param access ignored.
* @param permission ignored.
* @param enabled ignored.
* @see #getPermissions()
*/
@Override
@UnsupportedFileOperation
public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);
}
// - Import / export -------------------------------------------------------
// -------------------------------------------------------------------------
@Override
public InputStream getInputStream() throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
BookmarkBuilder builder = BookmarkManager.getBookmarkWriter(stream);
try {
builder.startBookmarks();
builder.addBookmark(bookmark.getName(), bookmark.getLocation(), bookmark.getParent());
builder.endBookmarks();
} catch (Throwable e) {
// If an exception occurred, we have to look for its root cause.
Throwable e2;
// Looks for the cause.
while ((e2 = e.getCause()) != null) {
e = e2;
}
// If the cause is an IOException, thow it.
if (e instanceof IOException) {
throw (IOException)e;
}
// Otherwise, throw the exception as an IOException with a the underlying cause's message.
throw new IOException(e.getMessage());
}
return new ByteArrayInputStream(stream.toByteArray());
}
@Override
public OutputStream getOutputStream() throws IOException {return new BookmarkOutputStream();}
// - Unused methods --------------------------------------------------------
// -------------------------------------------------------------------------
// The following methods are not used by BookmarkFile. They will throw an exception or
// return an 'operation non supported' / default value.
@Override
@UnsupportedFileOperation
public void mkdir() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);}
@Override
public long getLastModifiedDate() {return 0;}
@Override
public PermissionBits getChangeablePermissions() {return PermissionBits.EMPTY_PERMISSION_BITS;}
@Override
@UnsupportedFileOperation
public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);}
@Override
public long getSize() {return -1;}
@Override
@UnsupportedFileOperation
public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);}
@Override
@UnsupportedFileOperation
public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);}
@Override
@UnsupportedFileOperation
public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);}
@Override
public Object getUnderlyingFileObject() {return null;}
@Override
public boolean isSymlink() {return false;}
@Override
public boolean isSystem() {return false;}
@Override
public String getOwner() {return null;}
@Override
public boolean canGetOwner() {return false;}
@Override
public String getGroup() {return null;}
@Override
public boolean canGetGroup() {return false;}
@Override
@UnsupportedFileOperation
public short getReplication() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);
}
@Override
@UnsupportedFileOperation
public long getBlocksize() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);
}
@Override
@UnsupportedFileOperation
public void changeReplication(short replication) throws IOException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/file/BookmarkOutputStream.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see bookmarks:// file system.
* @author Nicolas Rinaudo
*/
class BookmarkRoot extends ProtocolFile implements BookmarkListener {
/** Time at which the bookmarks were last modified. */
private long lastModified;
BookmarkRoot() throws IOException {this(FileURL.getFileURL(BookmarkProtocolProvider.BOOKMARK + "://"));}
BookmarkRoot(FileURL url) {
super(url);
lastModified = System.currentTimeMillis();
BookmarkManager.addBookmarkListener(this);
}
@Override
public AbstractFile[] ls() throws IOException {
// Retrieves all available bookmarks.
Object[] buffer = BookmarkManager.getBookmarks().toArray();
AbstractFile[] files = new AbstractFile[buffer.length];
// Creates the associated instances of BookmarkFile
for (int i = 0; i < files.length; i++) {
files[i] = new BookmarkFile((Bookmark)buffer[i]);
}
return files;
}
@Override
public String getName() {
return "";
}
@Override
public boolean isDirectory() {
return true;
}
/**
* Stores the current date as the date of last modification.
*/
public void bookmarksChanged() {
lastModified = System.currentTimeMillis();
}
/**
* Returns the date at which the bookmark list was last modified.
* @return the date at which the bookmark list was last modified.
*/
@Override
public long getLastModifiedDate() {
return lastModified;
}
// The following methods are not used by BookmarkFile. They will throw an exception,
// return an 'operation non supported' value or return a default value.
@Override
public AbstractFile getParent() {return null;}
@Override
@UnsupportedFileOperation
public void delete() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.DELETE);}
@Override
@UnsupportedFileOperation
public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);}
@Override
@UnsupportedFileOperation
public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RENAME);}
@Override
@UnsupportedFileOperation
public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);}
@Override
public long getSize() {return -1;}
@Override
public void setParent(AbstractFile parent) {}
@Override
public boolean exists() {return true;}
@Override
public FilePermissions getPermissions() {return BookmarkFile.PERMISSIONS;}
@Override
@UnsupportedFileOperation
public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);}
@Override
public PermissionBits getChangeablePermissions() {return PermissionBits.EMPTY_PERMISSION_BITS;}
@Override
public boolean isSymlink() {return false;}
@Override
public boolean isSystem() {return false;}
@Override
@UnsupportedFileOperation
public void mkdir() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);}
@Override
@UnsupportedFileOperation
public InputStream getInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.READ_FILE);}
@Override
@UnsupportedFileOperation
public OutputStream getOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);}
@Override
@UnsupportedFileOperation
public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);}
@Override
@UnsupportedFileOperation
public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);}
@Override
@UnsupportedFileOperation
public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);}
@Override
@UnsupportedFileOperation
public long getFreeSpace() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);}
@Override
@UnsupportedFileOperation
public long getTotalSpace() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);}
@Override
public Object getUnderlyingFileObject() {return null;}
@Override
public String getOwner() {return null;}
@Override
public boolean canGetOwner() {return false;}
@Override
public String getGroup() {return null;}
@Override
public boolean canGetGroup() {return false;}
@Override
@UnsupportedFileOperation
public short getReplication() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);
}
@Override
@UnsupportedFileOperation
public long getBlocksize() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);
}
@Override
@UnsupportedFileOperation
public void changeReplication(short replication) throws IOException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);
}
}
================================================
FILE: src/main/java/com/mucommander/bookmark/file/package.html
================================================
LinkedHashMap which provides fast retrieval and insertion
* operations.
*
* LinkedHashMap is slow at that.
* To minimize the impact this could have on performance, this operation is not systematically performed
* for each call to get() and set() methods, unless the cache is full.
* That means this implementation is not as aggressive as it could be in terms of releasing expired items' memory
* but favors performance instead, which is what caches are for.
*
* @author Maxence Bernard
*/
public class FastLRUCache
The LRUCache implementation must however guarantee two things :
*
*
*
* @author Maxence Bernard
*/
public abstract class LRUCachenull if:
*
*
*
* @param key the cached item's key
* @return the cached value corresponding to the specified key, or null if a value could not
* found or has expired
*/
public abstract V get(K key);
/**
* Adds a new key/value pair to the cache and marks it as the most recently used.
*
*
*
*
* @param key the key for the object to store
* @param value the value to cache
* @param timeToLive the time-to-live of the object in the cache in milliseconds, or -1 for no time-to-live,
* the object will just be removed when it becomes the least recently used one.
*/
public abstract void add(K key, V value, long timeToLive);
/**
* Convenience method, equivalent to add(key, value, -1).
* @param key key
* @param value value
*/
public synchronized void add(K key, V value) {
add(key, value, -1);
}
/**
* Removes all items from this cache, leaving the cache in the same state as when it was just created.
*/
public abstract void clearAll();
/**
* Returns the current size of this cache, i.e. the number of cached elements it contains.
*
Note: Some items that have expired and have not yet been removed might be accounted for
* in the returned size.
*
* @return the current size of this cache
*/
public abstract int size();
/**
* Tests this LRUCache for corruption and throws a RuntimeException if something is wrong.
*/
protected abstract void testCorruption() throws RuntimeException;
/**
* Test method : simple test case + stress/sanity test
*
* @param args command line arguments
*/
public static void main(String[] args) {
LRUCacheAssociationWriter is an {@link AssociationBuilder} that will send
* all build messages it receives into an XML stream (as defined in {@link AssociationsXmlConstants}).
*
* @author Nicolas Rinaudo
*/
public class AssociationWriter implements AssociationsXmlConstants, AssociationBuilder {
/** Where to write the custom command associations to. */
private final XmlWriter out;
/**
* Builds a new writer that will send data to the specified output stream.
* @param stream where to write the XML data.
* @throws IOException if an I/O error occurs.
*/
AssociationWriter(OutputStream stream) throws IOException {out = new XmlWriter(stream);}
/**
* Opens the root XML element.
*/
@Override
public void startBuilding() throws CommandException {
try {
out.startElement(ELEMENT_ROOT);
out.println();
} catch(IOException e) {
throw new CommandException(e);
}
}
/**
* Closes the root XML element.
*/
@Override
public void endBuilding() throws CommandException {
try {
out.endElement(ELEMENT_ROOT);
} catch(IOException e) {
throw new CommandException(e);
}
}
@Override
public void startAssociation(String command) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_COMMAND, command);
try {
out.startElement(ELEMENT_ASSOCIATION, attr);
out.println();
} catch(IOException e) {
throw new CommandException(e);
}
}
@Override
public void endAssociation() throws CommandException {
try {
out.endElement(ELEMENT_ASSOCIATION);
} catch(IOException e) {
throw new CommandException(e);
}
}
@Override
public void setMask(String mask, boolean isCaseSensitive) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, mask);
if (!isCaseSensitive) {
attr.add(ATTRIBUTE_CASE_SENSITIVE, VALUE_FALSE);
}
writeStandaloneElement(attr, ELEMENT_MASK);
}
@Override
public void setIsSymlink(boolean isSymlink) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, isSymlink ? VALUE_TRUE : VALUE_FALSE);
writeStandaloneElement(attr, ELEMENT_IS_SYMLINK);
}
@Override
public void setIsHidden(boolean isHidden) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, isHidden ? VALUE_TRUE : VALUE_FALSE);
writeStandaloneElement(attr, ELEMENT_IS_HIDDEN);
}
@Override
public void setIsReadable(boolean isReadable) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, isReadable ? VALUE_TRUE : VALUE_FALSE);
writeStandaloneElement(attr, ELEMENT_IS_READABLE);
}
@Override
public void setIsWritable(boolean isWritable) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, isWritable ? VALUE_TRUE : VALUE_FALSE);
writeStandaloneElement(attr, ELEMENT_IS_WRITABLE);
}
@Override
public void setIsExecutable(boolean isExecutable) throws CommandException {
XmlAttributes attr = new XmlAttributes();
attr.add(ATTRIBUTE_VALUE, isExecutable ? VALUE_TRUE : VALUE_FALSE);
writeStandaloneElement(attr, ELEMENT_IS_EXECUTABLE);
}
private void writeStandaloneElement(XmlAttributes attr, String name) throws CommandException {
try {
out.writeStandAloneElement(name, attr);
} catch (IOException e) {
throw new CommandException(e);
}
}
}
================================================
FILE: src/main/java/com/mucommander/command/AssociationsXmlConstants.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <!ELEMENT associations (association*)>
*
* <!ELEMENT association (filename?,symlink?,hidden?,readable?,writable?,executable?)>
* <!ATTLIST association command CDATA #REQUIRED>
*
* <!ELEMENT filename EMPTY>
* <!ATTLIST filename value CDATA #REQUIRED>
* <!ATTLIST filename case_sensitive (true|false) #IMPLIED>
*
* <!ELEMENT symlink EMPTY>
* <!ATTLIST symlink value (true|false) #REQUIRED>
*
* <!ELEMENT hidden EMPTY>
* <!ATTLIST hidden value (true|false) #REQUIRED>
*
* <!ELEMENT readable EMPTY>
* <!ATTLIST readable value (true|false) #REQUIRED>
*
* <!ELEMENT writable EMPTY>
* <!ATTLIST writable value (true|false) #REQUIRED>
*
* <!ELEMENT executable EMPTY>
* <!ATTLIST executable value (true|false) #REQUIRED>
*
*
* @see AssociationReader
* @see AssociationWriter
* @author Nicolas Rinaudo
*/
interface AssociationsXmlConstants {
/** Root element. */
String ELEMENT_ROOT = "associations";
/** Custom association definition element. */
String ELEMENT_ASSOCIATION = "association";
String ELEMENT_MASK = "filename";
String ELEMENT_IS_SYMLINK = "symlink";
String ELEMENT_IS_HIDDEN = "hidden";
String ELEMENT_IS_READABLE = "readable";
String ELEMENT_IS_WRITABLE = "writable";
String ELEMENT_IS_EXECUTABLE = "executable";
/** Name of the attribute containing the alias of the command to execute in this association. */
String ATTRIBUTE_COMMAND = "command";
String ATTRIBUTE_VALUE = "value";
String ATTRIBUTE_CASE_SENSITIVE = "case_sensitive";
String VALUE_TRUE = "true";
String VALUE_FALSE = "false";
}
================================================
FILE: src/main/java/com/mucommander/command/Command.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
* Command.
*
* It is important to remember that \ character will escape the following character and be removed from the tokens." character will escape all characters until the next occurrence of ", except for \." characters are not removed from the resulting tokens.
*
*
* $f is replaced by a file's full path.$n is replaced by a file's name.$e is replaced by a file's extension.$N is replaced by a file's name without its extension.$p is replaced by a file's parent's path.$j is replaced by the path of the folder in which the JVM was started.Command instance has been retrieved, execution tokens can be retrieved through the
* {@link #getTokens(AbstractFile)} method. This will return a tokenized version of the command and replace any
* keyword by the corresponding file value . It's also possible to skip keyword replacement through the {@link #getTokens()} method.
* null, defaults to alias).
* @param fileMask mask for files specification
*/
public Command(String alias, String command, CommandType type, String displayName, String fileMask) {
this.alias = alias;
this.type = type;
this.displayName = displayName;
this.command = command;
this.fileMask = fileMask;
}
/**
* Creates a new command.
*
* {@link #Command(String, String, CommandType, String, String)
* Command(}alias, command, {@link CommandType#NORMAL_COMMAND}, null, null)
* .
*
* @param alias alias of the command.
* @param command command that will be executed.
*/
public Command(String alias, String command) {
this(alias, command, CommandType.NORMAL_COMMAND, null, null);
}
/**
* Creates a new command.
* {@link #Command(String, String, CommandType, String, String) Command(}alias, command, type, null, null).
*
* @param alias alias of the command.
* @param command command that will be executed.
* @param type type of the command.
*/
public Command(String alias, String command, CommandType type) {
this(alias, command, type, null, null);
}
public Command(Command cmd) {
this.alias = cmd.getAlias();
this.type = cmd.getType();
this.displayName = cmd.getDisplayName();
this.command = cmd.getCommand();
this.fileMask = cmd.getFileMask();
}
/**
* Returns this command's tokens without performing keyword substitution.
*
* @return this command's tokens without performing keyword substitution.
*/
public synchronized String[] getTokens() {
return getTokens(command, (AbstractFile[]) null);
}
/**
* Returns this command's tokens, replacing keywords by the corresponding values from the specified file.
*
* @param file file from which to retrieve keyword substitution values.
* @return this command's tokens, replacing keywords by the corresponding values from the specified file.
*/
public synchronized String[] getTokens(AbstractFile file) {
return getTokens(command, file);
}
/**
* Returns this command's tokens, replacing keywords by the corresponding values from the specified fileset.
*
* @param files files from which to retrieve keyword substitution values.
* @return this command's tokens, replacing keywords by the corresponding values from the specified fileset.
*/
public synchronized String[] getTokens(FileSet files) {
return getTokens(command, files);
}
/**
* Returns this command's tokens, replacing keywords by the corresponding values from the specified files.
*
* @param files files from which to retrieve keyword substitution values.
* @return this command's tokens, replacing keywords by the corresponding values from the specified files.
*/
public synchronized String[] getTokens(AbstractFile[] files) {
return getTokens(command, files);
}
/**
* Returns the specified command's tokens without performing keyword substitution.
*
* @param command command to tokenize.
* @return the specified command's tokens without performing keyword substitution.
*/
public static String[] getTokens(String command) {
return getTokens(command, (AbstractFile[]) null);
}
/**
* Returns the specified command's tokens after replacing keywords by the corresponding values from the specified file.
*
* @param command command to tokenize.
* @param file file from which to retrieve keyword substitution values.
* @return the specified command's tokens after replacing keywords by the corresponding values from the specified file.
*/
public static String[] getTokens(String command, AbstractFile file) {
return getTokens(command, new AbstractFile[]{file});
}
/**
* Returns the specified command's tokens after replacing keywords by the corresponding values from the specified fileset.
*
* @param command command to tokenize.
* @param files file from which to retrieve keyword substitution values.
* @return the specified command's tokens after replacing keywords by the corresponding values from the specified fileset.
*/
public static String[] getTokens(String command, FileSet files) {
return getTokens(command, files.toArray(new AbstractFile[0]));
}
/**
* Returns the specified command's tokens after replacing keywords by the corresponding values from the specified files.
*
* @param command command to tokenize.
* @param files file from which to retrieve keyword substitution values.
* @return the specified command's tokens after replacing keywords by the corresponding values from the specified files.
*/
public static String[] getTokens(String command, AbstractFile[] files) {
Listtrue if the specified character is a legal keyword.
*
* @param keyword character to check.
* @return true if the specified character is a legal keyword, false otherwise.
*/
private static boolean isLegalKeyword(char keyword) {
return keyword == KEYWORD_PATH || keyword == KEYWORD_NAME || keyword == KEYWORD_PARENT ||
keyword == KEYWORD_VM_PATH || keyword == KEYWORD_EXTENSION || keyword == KEYWORD_NAME_WITHOUT_EXTENSION;
}
/**
* Gets the value from file that should be used to replace keyword.
*
* @param keyword character to replace.
* @param file file from which to retrieve the replacement value.
* @return the requested replacement value.
*/
private static String getKeywordReplacement(char keyword, AbstractFile file) {
return switch (keyword) {
case KEYWORD_PATH -> file.getAbsolutePath();
case KEYWORD_NAME -> file.getName();
case KEYWORD_PARENT -> {
AbstractFile parentFile = file.getParent();
yield parentFile == null ? "" : parentFile.getAbsolutePath();
}
case KEYWORD_VM_PATH -> new File(System.getProperty("user.dir")).getAbsolutePath();
case KEYWORD_EXTENSION -> {
String extension = file.getExtension();
yield extension != null ? extension : "";
}
case KEYWORD_NAME_WITHOUT_EXTENSION -> file.getNameWithoutExtension();
default -> throw new IllegalArgumentException();
};
}
/**
* Returns the original, un-tokenized command.
*
* @return the original, un-tokenized command.
*/
public synchronized String getCommand() {
return command;
}
/**
* Returns this command's alias.
*
* @return this command's alias.
*/
public synchronized String getAlias() {
return alias;
}
/**
* Returns the command's type.
*
* @return the command's type.
*/
public synchronized CommandType getType() {
return type;
}
public synchronized String getFileMask() {
return fileMask;
}
/**
* Returns the command's display name.
* true if the command's display name has been set.
*
* @return true if the command's display name has been set, false otherwise.
*/
synchronized boolean isDisplayNameSet() {
return displayName != null;
}
@Override
public int hashCode() {
int hashCode;
hashCode = alias.hashCode();
hashCode = hashCode * 31 + command.hashCode();
hashCode = hashCode * 31 + getDisplayName().hashCode();
hashCode = hashCode * 31 + type.hashCode();
return hashCode;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Command cmd)) {
return false;
}
return command.equals(cmd.command) && alias.equals(cmd.alias) && type == cmd.type &&
getDisplayName().equals(cmd.getDisplayName());
}
@Override
public int compareTo(@NotNull Command command) {
int buffer = getDisplayName().compareTo(command.getDisplayName());
if (buffer != 0) {
return buffer;
}
if ((buffer = getAlias().compareTo(command.getAlias())) != 0) {
return buffer;
}
return this.command.compareTo(command.command);
}
@Override
public String toString() {
return alias + (displayName == null ? "" : ":" + displayName) + ":" + command + (fileMask != null ? "[" + fileMask + "]" : "");
}
}
================================================
FILE: src/main/java/com/mucommander/command/CommandAssociation.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see CommandAssociation.
* @param command command that must be executed if the association is matched.
* @param filter IMAGE_FILTER that files must match in order to be taken into account by the association.
*/
CommandAssociation(Command command, FileFilter filter) {
this.command = command;
this.fileFilter = filter;
}
/**
* Returns true if the specified file matches the association.
* @param file file to match against the association.
* @return true if the specified file matches the association, false otherwise.
*/
public boolean accept(AbstractFile file) {return fileFilter.match(file);}
// - Command retrieval -----------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the command used in the association.
* @return the command used in the association.
*/
public Command getCommand() {return command;}
public FileFilter getFilter() {return fileFilter;}
}
================================================
FILE: src/main/java/com/mucommander/command/CommandBuilder.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see CommandBuilder allows classes to query these lists regardless of
* their source.
*
* CommandBuilder can rely on their methods to be called in the proper order,
* and on both their {@link #startBuilding()} and {@link #endBuilding()} methods to be called. Classes that
* interact with such instances must make sure this contract is respected.
*
*
* public class CommandPrinter implements CommandBuilder {
* public void startBuilding() {System.out.println("Beginning command list building...");}
*
* public void endBuilding() {System.out.println("Done.");}
*
* public void addCommand(Command command) throws CommandException {
* System.out.println(" - creating command '" + command.getCommand() + "' with alias '" + command.getAlias() + "'");
* }
* }
*
* Passing an instance of CommandPrinter to {@link CommandManager#buildCommands(CommandBuilder, CommandType)}
* will result in something like:
*
* Beginning command list building...
* - creating command 'open $f' with alias 'open'
* - creating command 'open -a Safari $f' with alias 'openURL'
* Done.
*
*
* @author Nicolas Rinaudo
* @see CommandReader
* @see CommandManager#buildCommands(CommandBuilder, CommandType)
*/
public interface CommandBuilder {
/**
* Notifies the builder that command building is about to start.
* @throws CommandException if an error occurs.
*/
void startBuilding() throws CommandException;
/**
* Notifies the builder that command building is finished.
* @throws CommandException if an error occurs.
*/
void endBuilding() throws CommandException;
/**
* Notifies the builder that a new command has been found.
* @param command command that has been found.
* @throws CommandException if an error occurs.
*/
void addCommand(Command command) throws CommandException;
}
================================================
FILE: src/main/java/com/mucommander/command/CommandException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see null if the default one should be used. */
private static AbstractFile associationFile;
/** Whether the associations were modified since the last time they were saved. */
private static boolean wereAssociationsModified;
/** Default name of the association XML file. */
private static final String DEFAULT_ASSOCIATION_FILE_NAME = "associations.xml";
// - Commands definition ---------------------------------------------------
// -------------------------------------------------------------------------
/** Default name of the custom commands file. */
private static final String DEFAULT_COMMANDS_FILE_NAME = "commands.xml";
/** All known commands. */
private static Mapnull if the default one should be used. */
private static AbstractFile commandsFile;
/** Whether the custom commands have been modified since the last time they were saved. */
private static boolean wereCommandsModified;
/** Default command used when no other command is found for a specific file type. */
private static Command defaultCommand;
/**
* Prevents instances of CommandManager from being created.
*/
private CommandManager() {}
// - Command handling ------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Returns the tokens that compose the command that must be executed to open the specified file.
* {@link #getTokensForFile(AbstractFile,boolean) getTokensForFile(}file, true).
*
* @param file file for which the opening command's tokens must be returned.
* @return the tokens that compose the command that must be executed to open the specified file.
*/
public static String[] getTokensForFile(AbstractFile file) {
return getTokensForFile(file, true);
}
/**
* Returns the tokens that compose the command that must be executed to open the specified file.
* @param file file for which the opening command's tokens must be returned.
* @param allowDefault whether to use the default command if none was found to match the specified file.
* @return the tokens that compose the command that must be executed to open the specified file, null if not found.
*/
public static String[] getTokensForFile(AbstractFile file, boolean allowDefault) {
Command command = getCommandForFile(file, allowDefault);
return command == null ? null : command.getTokens(file);
}
/**
* Returns the command that must be executed to open the specified file.
* {@link #getCommandForFile(AbstractFile,boolean) getCommandForFile(}file, true).
*
* @param file file for which the opening command must be returned.
* @return the command that must be executed to open the specified file.
*/
public static Command getCommandForFile(AbstractFile file) {
return getCommandForFile(file, true);
}
private static Command getCommandForFile(AbstractFile file, Listnull if not found.
*/
public static Command getCommandForFile(AbstractFile file, boolean allowDefault) {
Command command = getCommandForFile(file, associations);
// Goes through all known associations and checks whether file matches any.
if (command != null) {
return command;
}
// Goes through all system associations and checks whether file matches any.
command = getCommandForFile(file, systemAssociations);
if (command != null) {
return command;
}
// We haven't found a command explicitly associated with 'file', but we might have a generic file opener
if (defaultCommand != null) {
return defaultCommand;
}
// We don't have a generic file opener, return the 'self execute' command if we're allowed.
if (allowDefault) {
return RUN_AS_EXECUTABLE_COMMAND;
}
return null;
}
/**
* Returns a sorted collection of all registered commands.
* @return a sorted collection of all registered commands.
*/
public static Collectionnull otherwise.
*/
public static Command getCommandForAlias(String alias, AbstractFile file) {
ListsetAssociationFile(FileFactory.getFile(file)).
*
* @param path path to the custom associations file.
* @throws FileNotFoundException if file is not accessible.
* @see #getAssociationFile()
* @see #loadAssociations()
* @see #writeAssociations()
*/
public static void setAssociationFile(String path) throws FileNotFoundException {
AbstractFile file = FileFactory.getFile(path);
if (file == null) {
setAssociationFile(new File(path));
} else {
setAssociationFile(file);
}
}
/**
* Sets the path to the custom associations file.
* setAssociationFile(FileFactory.getFile(file.getAbsolutePath())).
*
* @param file path to the custom associations file.
* @throws FileNotFoundException if file is not accessible.
* @see #getAssociationFile()
* @see #loadAssociations()
* @see #writeAssociations()
*/
public static void setAssociationFile(File file) throws FileNotFoundException {
setAssociationFile(FileFactory.getFile(file.getAbsolutePath()));
}
/**
* Sets the path to the custom associations file.
* @param file path to the custom associations file.
* @throws FileNotFoundException if file is not accessible.
* @see #getAssociationFile()
* @see #loadAssociations()
* @see #writeAssociations()
*/
public static void setAssociationFile(AbstractFile file) throws FileNotFoundException {
if (file.isBrowsable()) {
throw new FileNotFoundException("Not a valid file: " + file.getAbsolutePath());
}
associationFile = file;
}
/**
* Loads the custom associations XML File.
* setCommandFile(FileFactory.getFile(file));.
*
* @param path path to the custom commands file.
* @throws FileNotFoundException if file is not accessible.
* @see #getCommandFile()
* @see #loadCommands()
* @see #writeCommands()
*/
public static void setCommandFile(String path) throws FileNotFoundException {
AbstractFile file = FileFactory.getFile(path);
if (file == null) {
setCommandFile(new File(path));
} else {
setCommandFile(file);
}
}
/**
* Sets the path to the custom commands file.
* setCommandFile(FileFactory.getFile(file.getAbsolutePath()));.
*
* @param file path to the custom commands file.
* @throws FileNotFoundException if file is not accessible.
* @see #getCommandFile()
* @see #loadCommands()
* @see #writeCommands()
*/
public static void setCommandFile(File file) throws FileNotFoundException {setCommandFile(FileFactory.getFile(file.getAbsolutePath()));}
/**
* Sets the path to the custom commands file.
* @param file path to the custom commands file.
* @throws FileNotFoundException if file is not accessible.
* @see #getCommandFile()
* @see #loadCommands()
* @see #writeCommands()
*/
public static void setCommandFile(AbstractFile file) throws FileNotFoundException {
// Makes sure file can be used as a command file.
if (file.isBrowsable()) {
throw new FileNotFoundException("Not a valid file: " + file.getAbsolutePath());
}
commandsFile = file;
}
/**
* Writes Normal registered commands to the custom commands file.
*
*
*
* @param value String representation of type to analyze.
* @return type equals {CommandsXmlConstants#VALUE_SYSTEM}, {@link #SYSTEM_COMMAND} will be returned.type equals {CommandsXmlConstants#VALUE_INVISIBLE}, {@link #INVISIBLE_COMMAND} will be returned.type's integer equivalent.
*/
public static CommandType parseCommandType(String value) {
if (value == null) {
return NORMAL_COMMAND;
}
for (CommandType type : CommandType.values()) {
if (value.equals(type.value)) {
return type;
}
}
return NORMAL_COMMAND;
}
@Override
public String toString() {
return value;
}
}
================================================
FILE: src/main/java/com/mucommander/command/CommandWriter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see CommandWriter is a {@link CommandBuilder} that will send
* all build messages it receives into an XML stream (as defined in {@link CommandsXmlConstants}).
*
* @author Nicolas Rinaudo
*/
public class CommandWriter implements CommandsXmlConstants, CommandBuilder {
/** Where to write the custom command associations to. */
private final XmlWriter out;
/**
* Builds a new writer that will send data to the specified output stream.
* @param stream where to write the XML data.
* @throws IOException if an IO error occurs.
*/
CommandWriter(OutputStream stream) throws IOException {
out = new XmlWriter(stream);
}
/**
* Opens the root XML element.
*/
public void startBuilding() throws CommandException {
try {
out.startElement(ELEMENT_ROOT);
out.println();
} catch(IOException e) {
throw new CommandException(e);
}
}
/**
* Closes the root XML element.
*/
public void endBuilding() throws CommandException {
try {
out.endElement(ELEMENT_ROOT);
} catch(IOException e) {
throw new CommandException(e);
}
}
/**
* Writes the specified command's XML description.
* @param command command that should be written.
* @throws CommandException if an error occurs.
*/
public void addCommand(Command command) throws CommandException {
// Builds the XML description of the command.
XmlAttributes attributes = new XmlAttributes();
attributes.add(ATTRIBUTE_ALIAS, command.getAlias());
attributes.add(ATTRIBUTE_VALUE, command.getCommand());
attributes.add(ATTRIBUTE_FILEMASK, command.getFileMask());
if (command.getType().toString() != null) {
attributes.add(ATTRIBUTE_TYPE, command.getType().toString());
}
if (command.isDisplayNameSet()) {
attributes.add(ATTRIBUTE_DISPLAY, command.getDisplayName());
}
// Writes the XML description.
try {
out.writeStandAloneElement(ELEMENT_COMMAND, attributes);
} catch(IOException e) {
throw new CommandException(e);
}
}
}
================================================
FILE: src/main/java/com/mucommander/command/CommandsXmlConstants.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <!ELEMENT commands (association*)>
*
* <!ELEMENT command EMPTY>
* <!ATTLIST command value CDATA #REQUIRED>
* <!ATTLIST command alias CDATA #REQUIRED>
* <!ATTLIST command type (system|invisible) #IMPLIED>
* <!ATTLIST command display CDATA #IMPLIED>
*
* Where:
*
*
*
* @see CommandReader
* @see CommandWriter
* @author Nicolas Rinaudo
*/
interface CommandsXmlConstants {
/** Root element. */
String ELEMENT_ROOT = "commands";
/** Custom command definition element. */
String ELEMENT_COMMAND = "command";
/** Name of the attribute containing a command's display name. */
String ATTRIBUTE_DISPLAY = "display";
/** Name of the attribute containing a command's alias. */
String ATTRIBUTE_ALIAS = "alias";
/** Name of the attribute containing a command's value. */
String ATTRIBUTE_VALUE = "value";
String ATTRIBUTE_FILEMASK = "filemask";
/** Name of the attribute containing a command's type. */
String ATTRIBUTE_TYPE = "type";
/** Describes system commands. */
String VALUE_SYSTEM = "system";
/** Describes invisible commands. */
String VALUE_INVISIBLE = "invisible";
}
================================================
FILE: src/main/java/com/mucommander/command/PermissionsFileFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see PermissionsFileFilter.
* @param permission permission that will be checked against as defined in {@link com.mucommander.commons.file.FilePermissions}.
* @param filter whether the specified permission flag must be set for a file to be accepted.
*/
PermissionsFileFilter(int permission, boolean filter) {
this.permission = permission;
this.filter = filter;
}
public boolean accept(AbstractFile file) {
return filter == file.getPermissions().getBitValue(USER_ACCESS, permission);
}
/**
* Returns the permission that this IMAGE_FILTER will check.
* @return the permission that this IMAGE_FILTER will check.
*/
public int getPermission() {
return permission;
}
/**
* Returns true if files must have the IMAGE_FILTER's permission flag set in order to be accepted.
* @return true if files must have the IMAGE_FILTER's permission flag set in order to be accepted, false otherwise.
*/
public boolean getFilter() {
return filter;
}
}
================================================
FILE: src/main/java/com/mucommander/command/package.html
================================================
Framework used to run system commands and associate them to file types.
================================================
FILE: src/main/java/com/mucommander/commons/DummyDecoratedFile.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2018 Oleg Trifonov
*
* trolCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* trolCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see DummyFile with overwritten {@link #toString()}} method that return a "nice" value
* for local files (without 'file://localhost/')
*
* @author Oleg Trifonov
*/
public class DummyDecoratedFile extends DummyFile {
private static final String LOCAL_FILE_PREFIX;
static {
String prefix = "file://" + FileURL.LOCALHOST;
LOCAL_FILE_PREFIX = OsFamily.WINDOWS.isCurrent() ? prefix + '/' : prefix;
}
public DummyDecoratedFile(FileURL url) {
super(url);
}
@Override
public String toString() {
String result = super.toString();
if (result.startsWith(LOCAL_FILE_PREFIX)) {
return result.substring(LOCAL_FILE_PREFIX.length());
}
return result;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/HasProgress.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2016 Oleg Trifonov
*
* trolCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* trolCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
*
* Enumeration into an Iterator.
* @author Nicolas Rinaudo
*/
public class EnumeratorEnumerator. */
private final Enumerationtrue if the iterator has more elements.
* (In other words, returns true if {@link #next() next} would return an element rather than throwing an exception.)
* @return true if the iterator has more elements, false otherwise.
*/
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
/**
* Returns the next element in the iteration.
* @return the next element in the iteration.
* @throws NoSuchElementException if there is no next element in the iteration.
*/
@Override
public T next() throws NoSuchElementException {
return enumeration.nextElement();
}
/**
* Operation not supported.
* @throws UnsupportedOperationException whenever this method is called.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/collections/VectorChangeListener.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see true if there are more sections in the history.
* @return true if there are more sections in the history.
*/
boolean hasSections() {
return !sections.empty();
}
/**
* Returns the next section in history.
* @return the next section in history.
*/
ConfigurationSection popSection() {
return sections.pop();
}
/**
* Move to the specified section.
* @param name name of the current section's subsection in which to move.
* @param create if true and name doesn't exist, it will be created.
* @return true if we could move to name, false otherwise.
*/
@Override
public boolean moveTo(String name, boolean create) {
if (super.moveTo(name, create)) {
sections.push(getSection());
return true;
}
return false;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/Configuration.java
================================================
package com.mucommander.commons.conf;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Base class for all configuration related tasks.
* Configuration instance's main goal is to act as a configuration data repository.
* Once created, it can be used to {@link #getVariable(String) retrieve}, {@link #removeVariable(String) delete}
* and {@link #setVariable(String,String) set} configuration variables.
*
* Naming conventions
*
* For example, startup_folder.right.last_folder is interpreted as a variable called
* last_folder contained in a section called right, itself contained in
* another section called startup_folder.
*
* Variable types
* com.mucommander.commons.conf really only handles one type of variables, strings, it offers
* tools to cast them as primitive Java types (int, long, float, double, boolean). This is done through the use
* of the various primitive types' class implementation parseXXX method.
* When a variable hasn't been set but ant attempt is made to cast it, the standard Java default value will
* be returned:
*
*
*
* null0000falseConfiguration file format
* Configuration data location
* Configuration provides read and write methods that accept streams as parameters, it's
* also possible to set the data source once and for all and let the API deal with the details. This can
* be achieved through the {@link #setSource(ConfigurationSource) setSource} method.
* Note that a default implementation, {@link FileConfigurationSource}, is provided. It covers the most
* common case of configuration sources, a local configuration file.
* For application writers who wish to be able to retrieve configuration files through a variety of file systems,
* we suggest creating a source using the com.mucommander.file API.
*
* Monitoring configuration changes
*
* Note that LISTENERS are stored as weak references, meaning that application writers must ensure that they keep
* direct references to the listener instances they register if they do not want them to be garbaged collected
* out of existence randomly.
*
* @author Nicolas Rinaudo
*/
public class Configuration {
/** Used to get access to the configuration source's input and output streams. */
private ConfigurationSource source;
/** Used to create objects that will read from the configuration source. */
private ConfigurationReaderFactory readerFactory;
/** Used to create objects that will write to the configuration source. */
private ConfigurationWriterFactory writerFactory;
/** Holds the content of the configuration file. */
private final ConfigurationSection root = new ConfigurationSection();
/** Contains all registered configuration LISTENERS, stored as weak references. */
private final WeakHashMapConfiguration.
* Configuration using the specified source.
* Configuration using the specified format.
* Configuration using the specified source and format.
* @param source where the resulting instance will look for its configuration data.
* @param reader factory for configuration readers.
* @param writer factory for configuration writers.
*/
public Configuration(ConfigurationSource source, ConfigurationReaderFactory reader, ConfigurationWriterFactory writer) {
setSource(source);
setReaderFactory(reader);
setWriterFactory(writer);
}
/**
* Sets the source that will be used to read and write configuration information.
* @param s new configuration source.
* @see #getSource()
*/
public void setSource(ConfigurationSource s) {
synchronized(sourceLock) {
source = s;
}
}
/**
* Returns the current configuration source.
* @return the current configuration source, or null if it hasn't been set.
* @see #setSource(ConfigurationSource)
*/
public ConfigurationSource getSource() {
synchronized(sourceLock) {
return source;
}
}
/**
* Sets the factory that will be used to create {@link ConfigurationReader reader} instances.
* null parameter.
*
* @param f factory that will be used to create reader instances.
* @see #getReaderFactory()
*/
void setReaderFactory(ConfigurationReaderFactory f) {
synchronized(readerLock) {
readerFactory = f;
}
}
/**
* Returns the factory that is being used to create {@link ConfigurationReader reader} instances.
* null parameter.
*
* @param f factory that will be used to create writer instances.
* @see #getWriterFactory()
*/
void setWriterFactory(ConfigurationWriterFactory f) {
synchronized(writerLock) {
writerFactory = f;
}
}
/**
* Returns the factory that is being used to create writer instances.
* in.
* @throws IOException if an I/O error occurs.
* @throws ConfigurationException if a configuration error occurs.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @see #read(InputStream)
* @see #read(ConfigurationReader)
* @see #read()
*/
synchronized void read(Reader in, ConfigurationReader reader) throws IOException, ConfigurationException {
reader.read(in, new ConfigurationLoader(root));
}
/**
* Loads configuration from the specified input stream.
* UTF-8 encoded.
*/
@Deprecated
public void read(InputStream in) throws ConfigurationException, IOException {
read(new InputStreamReader(in, StandardCharsets.UTF_8), getReaderFactory().getReaderInstance());
}
/**
* Loads configuration from the specified reader.
* in.
* @throws IOException if an I/O error occurs.
* @throws ConfigurationException if a configuration error occurs.
* @throws SourceConfigurationException if no {@link ConfigurationSource} has been set.
* @throws ConfigurationFormatException if a syntax error occurs in the configuration data.
* @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.
* @see #read(InputStream)
* @see #read()
* @see #read(Reader,ConfigurationReader)
*/
public void read(ConfigurationReader reader) throws IOException, ConfigurationException {
ConfigurationSource source = getSource(); // Configuration source.
// Makes sure the configuration source has been properly set.
if (source == null) {
throw new SourceConfigurationException("Configuration source hasn't been set.");
}
// Reads the configuration data.
try (Reader in = source.getReader()) {
read(in, reader);
}
}
/**
* Loads configuration.
* fromVar to toVar.
* fromVar will have been deleted. Note that if fromVar doesn't
* exist, but toVar does, toVar will be deleted.
*
*
*
* The removal event will always be triggered first.
*
* @param fromVar fully qualified name of the variable to rename.
* @param toVar fully qualified name of the variable that will receive fromVar is removed.toVar is set.fromVar's value.
*/
public void renameVariable(String fromVar, String toVar) {
setVariable(toVar, removeVariable(fromVar));
}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. Note that this doesn't
* mean the call failed, but that name's value was already equal to value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getVariable(String)
* @see #getVariable(String,String)
*/
public synchronized boolean setVariable(String name, String value) {
ConfigurationExplorer explorer = new ConfigurationExplorer(root); // Used to navigate to the variable's parent section.
// Moves to the parent section.
String buffer = moveToParent(explorer, name, true); // Buffer for the variable's name trimmed of section information.
// If the variable's value was actually modified, triggers an event.
if (explorer.getSection().setVariable(buffer, value)) {
triggerEvent(new ConfigurationEvent(this, name, value));
return true;
}
return false;
}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. This, however, is not a
* way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getIntegerVariable(String)
* @see #getVariable(String,int)
*/
public boolean setVariable(String name, int value) {
return setVariable(name, ConfigurationSection.getValue(value));
}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. This, however, is not a
* way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getListVariable(String,String)
* @see #getVariable(String,List,String)
*/
public boolean setVariable(String name, Listfalse if it didn't modify name's value. This, however, is not
* a way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getFloatVariable(String)
* @see #getVariable(String,float)
*/
public boolean setVariable(String name, float value) {return setVariable(name, ConfigurationSection.getValue(value));}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. This, however, is not a
* way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getBooleanVariable(String)
* @see #getVariable(String,boolean)
*/
public boolean setVariable(String name, boolean value) {
return setVariable(name, ConfigurationSection.getValue(value));
}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. This, however, is not a
* way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getLongVariable(String)
* @see #getVariable(String,long)
*/
public boolean setVariable(String name, long value) {
return setVariable(name, ConfigurationSection.getValue(value));
}
/**
* Sets the value of the specified variable.
* false if it didn't modify name's value. This, however, is not a
* way of indicating that the call failed: false is only ever returned if the previous value is equal
* to the new value.
*
* true if this call resulted in a modification of the variable's value,
* false otherwise.
* @see #getDoubleVariable(String)
* @see #getVariable(String,double)
*/
public boolean setVariable(String name, double value) {return setVariable(name, ConfigurationSection.getValue(value));}
/**
* Returns the value of the specified variable.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, null otherwise.
* @see #setVariable(String,String)
* @see #getVariable(String,String)
*/
public synchronized String getVariable(String name) {
// If the variable's 'path' doesn't exist, return null.
ConfigurationExplorer explorer = new ConfigurationExplorer(root); // Used to navigate to the variable's parent section.
name = moveToParent(explorer, name, false);
return name == null ? null : explorer.getSection().getVariable(name);
}
/**
* Returns the value of the specified variable as a {@link ValueList}.
* @param name fully qualified name of the variable whose value should be retrieved.
* @param separator character used to split the variable's value into a list.
* @return the variable's value if set, null otherwise.
* @see #setVariable(String,List,String)
* @see #getVariable(String,List,String)
*/
public ValueList getListVariable(String name, String separator) {
return ConfigurationSection.getListValue(getVariable(name), separator);
}
/**
* Returns the value of the specified variable as an integer.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, 0 otherwise.
* @throws NumberFormatException if the variable's value cannot be cast to an integer.
* @see #setVariable(String,int)
* @see #getVariable(String,int)
*/
public int getIntegerVariable(String name) {
return ConfigurationSection.getIntegerValue(getVariable(name));
}
/**
* Returns the value of the specified variable as a long.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, 0 otherwise.
* @throws NumberFormatException if the variable's value cannot be cast to a long.
* @see #setVariable(String,long)
* @see #getVariable(String,long)
*/
public long getLongVariable(String name) {
return ConfigurationSection.getLongValue(getVariable(name));
}
/**
* Returns the value of the specified variable as a float.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, 0 otherwise.
* @throws NumberFormatException if the variable's value cannot be cast to a float.
* @see #setVariable(String,float)
* @see #getVariable(String,float)
*/
public float getFloatVariable(String name) {
return ConfigurationSection.getFloatValue(getVariable(name));
}
/**
* Returns the value of the specified variable as a double.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, 0 otherwise.
* @throws NumberFormatException if the variable's value cannot be cast to a double.
* @see #setVariable(String,double)
* @see #getVariable(String,double)
*/
public double getDoubleVariable(String name) {
return ConfigurationSection.getDoubleValue(getVariable(name));
}
/**
* Returns the value of the specified variable as a boolean.
* @param name fully qualified name of the variable whose value should be retrieved.
* @return the variable's value if set, false otherwise.
* @see #setVariable(String,boolean)
* @see #getVariable(String,boolean)
*/
public boolean getBooleanVariable(String name) {
return ConfigurationSection.getBooleanValue(getVariable(name));
}
/**
* Checks whether the specified variable has been set.
* @param name fully qualified name of the variable to check for.
* @return true if the variable is set, false otherwise.
*/
public boolean isVariableSet(String name) {
return getVariable(name) != null;
}
/**
* Prunes dead branches from the specified configuration tree.
* @param explorer used to backtrack through the configuration tree.
*/
private void prune(BufferedConfigurationExplorer explorer) {
// If we're at the root level, nothing to prune.
if (!explorer.hasSections()) {
return;
}
ConfigurationSection current = explorer.popSection();
// Look for branches to prune until we've either found a non-empty one
// or reached the root of the three.
while (current.isEmpty() && current != root) {
// Gets the current section's parent and prune.
ConfigurationSection parent = explorer.hasSections() ? explorer.popSection() : root;
parent.removeSection(current);
current = parent;
}
}
/**
* Deletes the specified variable from the configuration.
* null if it wasn't set.
*/
public synchronized String removeVariable(String name) {
BufferedConfigurationExplorer explorer = new BufferedConfigurationExplorer(root); // Used to navigate to the variable's parent section.
String buffer = moveToParent(explorer, name, false); // Buffer for the variable's name trimmed of section information.
// If the variable's 'path' doesn't exist, return null.
if (buffer == null) {
return null;
}
// If the variable was actually set, triggers an event.
if ((buffer = explorer.getSection().removeVariable(buffer)) != null) {
prune(explorer);
triggerEvent(new ConfigurationEvent(this, name, null));
}
return buffer;
}
/**
* Deletes the specified variable from the configuration.
* null if it wasn't set.
*/
public ValueList removeListVariable(String name, String separator) {
return ConfigurationSection.getListValue(removeVariable(name), separator);
}
/**
* Deletes the specified variable from the configuration.
* 0 if it wasn't set.
*/
public int removeIntegerVariable(String name) {
return ConfigurationSection.getIntegerValue(removeVariable(name));
}
/**
* Deletes the specified variable from the configuration.
* 0 if it wasn't set.
*/
public long removeLongVariable(String name) {
return ConfigurationSection.getLongValue(removeVariable(name));
}
/**
* Deletes the specified variable from the configuration.
* 0 if it wasn't set.
*/
public float removeFloatVariable(String name) {
return ConfigurationSection.getFloatValue(removeVariable(name));
}
/**
* Deletes the specified variable from the configuration.
* 0 if it wasn't set.
*/
public double removeDoubleVariable(String name) {
return ConfigurationSection.getDoubleValue(removeVariable(name));
}
/**
* Deletes the specified variable from the configuration.
* false if it wasn't set.
*/
public boolean removeBooleanVariable(String name) {
return ConfigurationSection.getBooleanValue(removeVariable(name));
}
/**
* Remove all variables and sub-sections under the root section
*/
public void clear() {
root.clear();
}
// - Advanced variable retrieval -----------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
/**
* Retrieves the value of the specified variable.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @see #setVariable(String,String)
* @see #getVariable(String)
*/
public synchronized String getVariable(String name, String defaultValue) {
ConfigurationExplorer explorer = new ConfigurationExplorer(root); // Used to navigate to the variable's parent section.
// Navigates to the parent section. We do not have to check for null values here,
// as the section will be created if it doesn't exist.
String buffer = moveToParent(explorer, name, true); // Buffer for the variable's name trimmed of section information.
// If the variable isn't set, set it to defaultValue and triggers an event.
String value = explorer.getSection().getVariable(buffer); // Buffer for the variable's value.
if (value == null) {
explorer.getSection().setVariable(buffer, defaultValue);
triggerEvent(new ConfigurationEvent(this, name, defaultValue));
return defaultValue;
}
return value;
}
/**
* Retrieves the value of the specified variable as a {@link ValueList}.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if variable name is not set.
* @param separator separator to use for defaultValue if variable name is not set.
* @return the specified variable's value.
* @see #setVariable(String,List,String)
* @see #getListVariable(String,String)
*/
public ValueList getVariable(String name, ListdefaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @throws NumberFormatException if the variable's value cannot be cast to an integer.
* @see #setVariable(String,int)
* @see #getIntegerVariable(String)
*/
public int getVariable(String name, int defaultValue) {
return ConfigurationSection.getIntegerValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));
}
/**
* Retrieves the value of the specified variable as a long.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @throws NumberFormatException if the variable's value cannot be cast to a long.
* @see #setVariable(String,long)
* @see #getLongVariable(String)
*/
public long getVariable(String name, long defaultValue) {
return ConfigurationSection.getLongValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));
}
/**
* Retrieves the value of the specified variable as a float.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @throws NumberFormatException if the variable's value cannot be cast to a float.
* @see #setVariable(String,float)
* @see #getFloatVariable(String)
*/
public float getVariable(String name, float defaultValue) {
return ConfigurationSection.getFloatValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));
}
/**
* Retrieves the value of the specified variable as a boolean.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @see #setVariable(String,boolean)
* @see #getBooleanVariable(String)
*/
public boolean getVariable(String name, boolean defaultValue) {
return ConfigurationSection.getBooleanValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));
}
/**
* Retrieves the value of the specified variable as a double.
* defaultValue before
* returning it. If this happens, a configuration {@link ConfigurationEvent event} will
* be sent to all registered LISTENERS.
*
* @param name name of the variable to retrieve.
* @param defaultValue value to use if name is not set.
* @return the specified variable's value.
* @throws NumberFormatException if the variable's value cannot be cast to a double.
* @see #setVariable(String,double)
* @see #getDoubleVariable(String)
*/
public double getVariable(String name, double defaultValue) {
return ConfigurationSection.getDoubleValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));
}
// - Helper methods ------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
/**
* Navigates the specified explorer to the parent section of the specified variable.
* @param root where to start exploring from.
* @param name name of the variable to seek.
* @param create whether the path to the variable should be created if it doesn't exist.
* @return the name of the variable trimmed of section information, null if not found.
*/
private String moveToParent(ConfigurationExplorer root, String name, boolean create) {
// Goes through each element of the path.
StringTokenizer parser = new StringTokenizer(name, ".");
while (parser.hasMoreTokens()) {
// If we've reached the variable's name, return it.
name = parser.nextToken();
if (!parser.hasMoreTokens())
return name;
// If we've reached a dead-end, return null.
if (!root.moveTo(name, create)) {
return null;
}
}
return name;
}
// - Configuration listening ---------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
/**
* Adds the specified object to the list of registered configuration LISTENERS.
* @param listener object to register as a configuration listener.
* @see #removeConfigurationListener(ConfigurationListener)
*/
public void addConfigurationListener(ConfigurationListener listener) {
LISTENERS.put(listener, null);}
/**
* Removes the specified object from the list of registered configuration LISTENERS.
* @param listener object to remove from the list of registered configuration LISTENERS.
* @see #addConfigurationListener(ConfigurationListener)
*/
public void removeConfigurationListener(ConfigurationListener listener) {
LISTENERS.remove(listener);}
/**
* Passes the specified event to all registered configuration LISTENERS.
* @param event event to propagate.
*/
private void triggerEvent(ConfigurationEvent event) {
for (ConfigurationListener listener : LISTENERS.keySet()) {
listener.configurationChanged(event);
}
}
/**
* Returns the configuration's root section.
* @return the configuration's root section.
*/
ConfigurationSection getRoot() {
return root;
}
/**
* Used to load configuration.
* @author Nicolas Rinaudo
*/
private class ConfigurationLoader implements ConfigurationBuilder {
/** Parents of {@link #currentSection}. */
private Stackcom.mucommander.commons.conf API comes with a default no-op implementation,
* {@link DefaultConfigurationBuilder}. This can be used instead of ConfigurationBuilder
* when only a subset of the possible events are of interest.
*
* @author Nicolas Rinaudo
* @see DefaultConfigurationBuilder
*/
public interface ConfigurationBuilder {
/**
* Receives notification at the beginning of the configuration.
* startSection event, even if the section is empty. All the section's content will be
* reported, in order, before the corresponding {@link #endSection(String) endSection} event.
*
* @param name name of the new section.
* @throws ConfigurationException any Configuration error, possibly wrapping another exception.
*/
void startSection(String name) throws ConfigurationException;
/**
* Receives notification at the end of a section.
* endSection
* event, even if the section is empty.
*
* @param name name of the finished section.
* @throws ConfigurationException any Configuration error, possibly wrapping another exception.
*/
void endSection(String name) throws ConfigurationException;
/**
* Receives notification of variable definition.
* ConfigurationEvent when the event occurs.
*
* @see ConfigurationListener
* @see Configuration
* @author Nicolas Rinaudo
*/
public class ConfigurationEvent {
/** Name of the variable that has been modified. */
private final String name;
/** Variable's new value. */
private final String value;
/** Configuration to which the event relates. */
private final Configuration configuration;
/**
* Creates a new configuration event.
* name in configuration
* configuration, and indicate that it has been set to value.
*
* null is an accepted value for parameter value, and will be
* interpreted to mean that the variable has been deleted.
*
* @param configuration configuration to which the event relates.
* @param name name of the variable that was modified.
* @param value value of the variable that was modified.
*/
public ConfigurationEvent(Configuration configuration, String name, String value) {
this.name = name;
this.value = value;
this.configuration = configuration;
}
/**
* Returns the configuration to which the event relates.
* @return the configuration to which the event relates.
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* Returns the name of the variable that was modified.
* test.somevar, this is what this method will return,
* not somevar.
*
* @return the name of the variable that was modified.
*/
public String getVariable() {
return name;
}
/**
* Returns the new value for the modified variable.
* null.
*
* @return the new value for the modified variable.
*/
public String getValue() {
return value;
}
/**
* Returns the new value for the modified variable cast as an integer.
* false.
*
* @return the new value for the modified variable.
*/
public boolean getBooleanValue() {
return ConfigurationSection.getBooleanValue(value);
}
/**
* Returns the new value for the modified variable as a long.
* com.mucommander.commons.conf API
* or the application. Application writers can subclass it to provide additional functionality. Different
* classes of the com.mucommander.commons.conf API may throw this exception or any exception subclassed
* from it.
* ConfigurationException or an exception derived from it.
*
* @author Nicolas Rinaudo
*/
public class ConfigurationException extends Exception {
/**
* Creates a new configuration exception.
* @param message the error message.
*/
public ConfigurationException(String message) {super(message);}
/**
* Creates a new configuration exception wrapping an existing exception.
* ConfigurationException.
*
* @param cause the exception to be wrapped in a ConfigurationException.
*/
public ConfigurationException(Throwable cause) {super(cause);}
/**
* Creates a new configuration exception from an existing exception.
* ConfigurationException.
*/
public ConfigurationException(String message, Throwable cause) {super(message, cause);}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationExplorer.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see true and name doesn't exist, it will be created.
* @return true if we could move to name, false otherwise.
*/
public boolean moveTo(String name, boolean create) {
ConfigurationSection buffer = section.getSection(name); // Buffer for the subsection.
// Checks whether the requested subsection exists.
if (buffer == null) {
// If it doesn't exist, either return false or create it depending on parameters.
if (create) {
section = section.addSection(name);
return true;
}
return false;
}
section = buffer;
return true;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationFormatException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see com.mucommander.commons.conf API, format errors
* are syntax errors in a configuration source.
* ConfigurationFormatException might provide information
* about the position in the source at which the error occurred. See the documentation of
* {@link #getLineNumber() getLineNumber} and {@link #getColumnNumber() getColumnNumber} for more
* information on location conventions.
* ConfigurationFormatException subclasses {@link ConfigurationException}, it
* inherits his capacity to wrap other exceptions.
*
* @author Nicolas Rinaudo
*/
public class ConfigurationFormatException extends ConfigurationException {
/** Describes an unknown {@link #getLineNumber() line} or {@link #getColumnNumber() column} value.*/
public static final int UNKNOWN_LOCATION = -1;
/** Line at which the error occurred. */
private int line = UNKNOWN_LOCATION;
/** Column at which the error occurred. */
private int column = UNKNOWN_LOCATION;
/**
* Creates a new configuration format exception.
* @param message the error message.
*/
public ConfigurationFormatException(String message) {super(message);}
/**
* Creates a new configuration format exception.
* line and column.
* See the documentation of {@link #getLineNumber() getLineNumber} and
* {@link #getColumnNumber() getColumnNumber} for more information on location conventions.
*
* @param message the error message.
* @param line line at which the error occurred.
* @param column column at which the error occurred.
*/
public ConfigurationFormatException(String message, int line, int column) {
this(message);
setLocationInformation(line, column);
}
/**
* Creates a new configuration format exception wrapping an existing exception.
* ConfigurationFormatException.
*
* @param cause the exception to be wrapped in a ConfigurationFormatException.
*/
public ConfigurationFormatException(Throwable cause) {super(cause == null ? null : cause.getMessage(), cause);}
/**
* Creates a new configuration format exception wrapping an existing exception.
* ConfigurationFormatException.
* line and column.
* See the documentation of {@link #getLineNumber() getLineNumber} and
* {@link #getColumnNumber() getColumnNumber} for more information on location conventions.
*
* @param cause the exception to be wrapped in a ConfigurationFormatException.
* @param line line at which the error occurred.
* @param column column at which the error occurred.
*/
public ConfigurationFormatException(Throwable cause, int line, int column) {
this(cause);
setLocationInformation(line, column);
}
/**
* Creates a new configuration format exception from an existing exception.
* ConfigurationFormatException.
*/
public ConfigurationFormatException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new configuration format exception from an existing exception.
* line and column.
* See the documentation of {@link #getLineNumber() getLineNumber} and
* {@link #getColumnNumber() getColumnNumber} for more information on location conventions.
*
* @param message the detail message.
* @param cause the exception to be wrapped in a ConfigurationFormatException.
* @param line line at which the error occurred.
* @param column column at which the error occurred.
*/
public ConfigurationFormatException(String message, Throwable cause, int line, int column) {
this(message, cause);
setLocationInformation(line, column);
}
/**
* Sets the position in the stream at which the error occurred.
* @param line line at which the error occurred.
* @param column column at which the error occurred.
*/
private void setLocationInformation(int line, int column) {
this.line = line;
this.column = column;
}
/**
* Returns the line at which the error occurred.
* 1 will describe the
* first line in the configuration source.
*
* @return the line at which the error occurred, {@link #UNKNOWN_LOCATION} if the information is not
* available or relevant.
* @see #getColumnNumber()
*/
public int getLineNumber() {return line;}
/**
* Returns the column at which the error occurred.
* 1 will describe the first character in the current line.
*
* @return the column at which the error occurred, {@link #UNKNOWN_LOCATION} if the information is not
* available or relevant.
* @see #getLineNumber()
*/
public int getColumnNumber() {return column;}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationListener.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see com.mucommander.commons.conf package comes with a default implementation,
* {@link XmlConfigurationReader}, which handles the standard muCommander configuration file format.
* ConfigurationReader to be usable by
* {@link Configuration configuration} instances, it must come with an associated implementation of
* {@link ConfigurationReaderFactory}.
* ConfigurationException.getCause().
*/
void read(Reader in, ConfigurationBuilder builder) throws ConfigurationException, IOException;
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationReaderFactory.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ConfigurationReaderFactory's sole purpose is to create instances of {@link ConfigurationReader}. In most cases, a
* factory class will be associated with a reader class, and its code will look something like:
*
* public class MyReaderFactory implements ConfigurationReaderFactory {
* public ConfigurationReader getReaderInstance() {return new MyReader();}
* }
*
*
* @author Nicolas Rinaudo
* @see ConfigurationReader
*/
public interface ConfigurationReaderFactorynull if none.
*/
public String removeVariable(String name) {
return variables.remove(name);
}
/**
* Returns the value of the specified variable.
* @param name name of the variable whose value should be returned.
* @return the value of the specified variable, or null if it wasn't set.
*/
public String getVariable(String name) {
return variables.get(name);
}
/**
* Sets the specified variable to the specified value.
* value is either null or an empty string,
* the call will be equivalent to {@link #removeVariable(String)}.
*
* @param name name of the variable to set.
* @param value value for the variable.
* @return true if the variable's value was changed as a result of this call, false
* otherwise.
*/
public boolean setVariable(String name, String value) {
// If the specified value is empty, deletes the variable.
if (value == null || value.trim().isEmpty()) {
// If the variable wasn't set, we haven't changed its value.
if (getVariable(name) == null) {
return false;
}
// Otherwise, deletes it and returns true.
removeVariable(name);
return true;
}
// Compares the variable's new and old values.
String buffer = variables.put(name, value);
return buffer == null || !buffer.equals(value);
}
/**
* Returns a set on the names of the variables that are defined in the section.
* true if the section contains any variable.
* @return true if the section contains any variable, false otherwise.
*/
public boolean hasVariables() {
return !variables.isEmpty();
}
/**
* Casts the specified value into an integer.
* value is null, this method will return 0.
*
* @param value value to cast to an integer.
* @return value as an integer.
*/
public static int getIntegerValue(String value) {
return value == null ? 0 : Integer.parseInt(value);
}
/**
* Casts the specified value into a value list.
* value is null, this method will return null.
*
* @param value value to cast to a value list.
* @param separator string used to separate data in tokens.
* @return value as a value list.
*/
public static ValueList getListValue(String value, String separator) {
return value == null ? null : new ValueList(value, separator);
}
/**
* Casts the specified value into a float.
* value is null, this method will return 0.
*
* @param value value to cast to a float.
* @return value as a float.
*/
public static float getFloatValue(String value) {
return value == null ? 0 : Float.parseFloat(value);
}
/**
* Casts the specified value into an boolean.
* value is null, this method will return false.
*
* @param value value to cast to an boolean.
* @return value as an boolean.
*/
public static boolean getBooleanValue(String value) {
return Boolean.TRUE.toString().equals(value);
}
/**
* Casts the specified value into an long.
* value is null, this method will return 0.
*
* @param value value to cast to an long.
* @return value as an long.
*/
public static long getLongValue(String value) {
return value == null ? 0 : Long.parseLong(value);
}
/**
* Casts the specified value into an double.
* value is null, this method will return 0.
*
* @param value value to cast to an double.
* @return value as an double.
*/
public static double getDoubleValue(String value) {
return value == null ? 0 : Double.parseDouble(value);
}
/**
* Casts the specified value into a string.
* @param value value to cast as a string.
* @return value as a string.
*/
public static String getValue(int value) {
return Integer.toString(value);
}
/**
* Casts the specified value into a string.
* @param value value to cast as a string.
* @param separator string to use as a separator.
* @return value as a string.
*/
public static String getValue(Listvalue as a string.
*/
public static String getValue(float value) {
return Float.toString(value);
}
/**
* Casts the specified value into a string.
* @param value value to cast as a string.
* @return value as a string.
*/
public static String getValue(boolean value) {
return Boolean.toString(value);
}
/**
* Casts the specified value into a string.
* @param value value to cast as a string.
* @return value as a string.
*/
public static String getValue(long value) {
return Long.toString(value);
}
/**
* Casts the specified value into a string.
* @param value value to cast as a string.
* @return value as a string.
*/
public static String getValue(double value) {
return Double.toString(value);
}
// - Section access ------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
/**
* Creates a subsection wit the specified name in the section.
* null otherwise.
*/
public ConfigurationSection removeSection(String name) {
return sections.remove(name);
}
/**
* Deletes the specified section.
* true if the specified section was removed, false if it didn't exist.
*/
public boolean removeSection(ConfigurationSection section) {
Setnull otherwise.
*/
public ConfigurationSection getSection(String name) {
return sections.get(name);
}
/**
* Returns an set on all of this section's subsections' names.
* true if this section has subsections.
* @return true if this section has subsections, false otherwise.
*/
public boolean hasSections() {
return !sections.isEmpty();
}
/**
* Returns true if the section doesn't contain either variables or sub-sections.
* true if the section doesn't contain either variables or sub-sections, false
* otherwise.
*/
public boolean isEmpty() {
return !hasSections() && !hasVariables();
}
/**
* Remove all variables and sub-sections of the section
*/
public void clear() {
variables.clear();
sections.clear();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationSource.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see com.mucommander.commons.conf package comes with a default implementation,
* {@link FileConfigurationSource},
* which will open input and output streams on a local file.
*
* @author Nicolas Rinaudo
* @see FileConfigurationSource
*/
public interface ConfigurationSource {
/**
* Returns an input stream on the configuration source.
* @return an input stream on the configuration source.
* @throws IOException if any I/O error occurs.
*/
Reader getReader() throws IOException;
/**
* Returns an output stream on the configuration source.
* @return an output stream on the configuration source.
* @throws IOException if any I/O error occurs.
*/
Writer getWriter() throws IOException;
/**
* Returns whether this source exists
* @return true if the source exists, false otherwise.
*
* @throws IOException if any I/O error occurs.
*/
boolean isExists() throws IOException;
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationStructureException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see com.mucommander.commons.conf API, structure errors
* are inconsistencies in the structure of the configuration tree - a section closed more
* than once, for example, or never closed at all.
* ConfigurationStructureException subclasses {@link ConfigurationException}, it
* inherits his capacity to wrap other exceptions.
*
* @author Nicolas Rinaudo
*/
public class ConfigurationStructureException extends ConfigurationException {
/**
* Creates a new configuration structure exception.
* @param message the error message.
*/
public ConfigurationStructureException(String message) {super(message);}
/**
* Creates a new configuration structure exception wrapping an existing exception.
* ConfigurationStructureException.
*
* @param cause the exception to be wrapped in a ConfigurationStructureException.
*/
public ConfigurationStructureException(Throwable cause) {super(cause);}
/**
* Creates a new configuration structure exception from an existing exception.
* ConfigurationStructureException.
*/
public ConfigurationStructureException(String message, Throwable cause) {super(message, cause);}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ConfigurationWriterFactory.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ConfigurationWriterFactory's sole purpose is to create instances of writer. In most cases, a
* factory class will be associated with a writer class, and its code will look something like:
*
* public class MyWriterFactory implements ConfigurationWriterFactory {
* public ConfigurationWriter getWriterInstance() {return new MyWriter();}
* }
*
*
* @author Nicolas Rinaudo
*/
public abstract class ConfigurationWriterFactoryUTF-8 encoded.
*/
@Deprecated
public FileConfigurationSource(File file) {
this(file, "utf-8");
}
/**
* Creates a source on the specified file and charset.
* @param file file in which the configuration data is located.
* @param charset charset in which the file is encoded.
*/
public FileConfigurationSource(File file, Charset charset) {
this.file = file;
this.charset = charset;
}
/**
* Creates a source on the specified file and charset.
* @param file file in which the configuration data is located.
* @param charset charset in which the file is encoded.
*/
FileConfigurationSource(File file, String charset) {
this(file, Charset.forName(charset));
}
/**
* Creates a source that will open streams on the specified file.
* @param path path to the file in which the configuration data is located.
* @deprecated Application developers should use {@link #FileConfigurationSource(String, Charset)} instead. This
* constructor assumes the specified file to be UTF-8 encoded.
*/
@Deprecated
public FileConfigurationSource(String path) {
this(new File(path));
}
/**
* Creates a source on the specified file and charset.
* @param path path to the file in which the configuration data is located.
* @param charset charset in which the file is encoded.
*/
FileConfigurationSource(String path, String charset) {
this(new File(path), charset);
}
/**
* Creates a source on the specified file and charset.
* @param path path to the file in which the configuration data is located.
* @param charset charset in which the file is encoded.
*/
public FileConfigurationSource(String path, Charset charset) {
this(new File(path), charset);
}
/**
* Returns the file on which input and output streams are opened.
* @return the file on which input and output streams are opened.
*/
public File getFile() {
return file;
}
/**
* Returns the charset in which the {@link #getFile() configuration file} is encoded.
* @return the charset in which the {@link #getFile() configuration file} is encoded.
*/
public Charset getCharset() {
return charset;
}
@Override
public Reader getReader() throws IOException {
InputStream is = new FileInputStream(file);
return new BufferedReader(new InputStreamReader(is, charset));
}
@Override
public Writer getWriter() throws IOException {
return new OutputStreamWriter(new FileOutputStream(file), charset);
}
@Override
public boolean isExists() {
return file.exists();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ReaderConfigurationException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ReaderConfigurationException subclasses {@link ConfigurationException}, it
* inherits his capacity to wrap other exceptions.
*
* @author Nicolas Rinaudo
*/
public class ReaderConfigurationException extends ConfigurationException {
/**
* Creates a new reader configuration exception.
* @param message the error message.
*/
public ReaderConfigurationException(String message) {super(message);}
/**
* Creates a new reader configuration exception wrapping an existing exception.
* ReaderConfigurationException.
*
* @param cause the exception to be wrapped in a ReaderConfigurationException.
*/
public ReaderConfigurationException(Throwable cause) {super(cause);}
/**
* Creates a new reader configuration exception from an existing exception.
* ReaderConfigurationException.
*/
public ReaderConfigurationException(String message, Throwable cause) {super(message, cause);}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/SourceConfigurationException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see SourceConfigurationException subclasses {@link ConfigurationException}, it
* inherits his capacity to wrap other exceptions.
*
* @author Nicolas Rinaudo
*/
public class SourceConfigurationException extends ConfigurationException {
/**
* Creates a new source configuration exception.
* @param message the error message.
*/
public SourceConfigurationException(String message) {super(message);}
/**
* Creates a new source configuration exception wrapping an existing exception.
* SourceConfigurationException.
*
* @param cause the exception to be wrapped in a SourceConfigurationException.
*/
public SourceConfigurationException(Throwable cause) {super(cause);}
/**
* Creates a new source configuration exception from an existing exception.
* SourceConfigurationException.
*/
public SourceConfigurationException(String message, Throwable cause) {super(message, cause);}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ValueIterator.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ValueIterator wrapping the specified iterator.
* @param iterator iterator to wrap.
*/
ValueIterator(Iteratortrue if the iteration has more elements.
* (In other words, returns true if next would return an element rather than throwing an exception.)
* @return true if the iteration has more elements.
*/
@Override
public boolean hasNext() {
return iterator.hasNext();
}
/**
* Returns the next element in the iteration.
* @return the next element in the iteration.
* @throws NoSuchElementException if the iteration has no more elements.
*/
@Override
public String next() {
return iterator.next();
}
/**
* Throws an UnsupportedOperationException.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Returns the next value in the iterator as a string.
* @return the next value in the iterator as a string.
* @throws NoSuchElementException if the iteration has no more elements.
*/
public String nextValue() {
return iterator.next();
}
/**
* Returns the next value in the iterator as a integer.
* @return the next value in the iterator as a integer.
* @throws NoSuchElementException if the iteration has no more elements.
* @throws NumberFormatException if the value cannot be cast to an integer.
*/
public int nextIntegerValue() {
return ConfigurationSection.getIntegerValue(nextValue());
}
/**
* Returns the next value in the iterator as a float.
* @return the next value in the iterator as a float.
* @throws NoSuchElementException if the iteration has no more elements.
* @throws NumberFormatException if the value cannot be cast to a float.
*/
public float nextFloatValue() {
return ConfigurationSection.getFloatValue(nextValue());
}
/**
* Returns the next value in the iterator as a long.
* @return the next value in the iterator as a long.
* @throws NoSuchElementException if the iteration has no more elements.
* @throws NumberFormatException if the value cannot be cast to a long.
*/
public long nextLongValue() {
return ConfigurationSection.getLongValue(nextValue());
}
/**
* Returns the next value in the iterator as a double.
* @return the next value in the iterator as a double.
* @throws NoSuchElementException if the iteration has no more elements.
* @throws NumberFormatException if the value cannot be cast to a double.
*/
public double nextDoubleValue() {
return ConfigurationSection.getDoubleValue(nextValue());
}
/**
* Returns the next value in the iterator as a boolean.
* @return the next value in the iterator as a boolean.
* @throws NoSuchElementException if the iteration has no more elements.
*/
public boolean nextBooleanValue() {
return ConfigurationSection.getBooleanValue(nextValue());
}
/**
* Returns the next value in the iterator as a {@link ValueList}.
* @param separator stirng used to tokenise the next value.
* @return the next value in the iterator as a {@link ValueList}.
* @throws NoSuchElementException if the iteration has no more elements.
*/
public ValueList nextListValue(String separator) {
return ConfigurationSection.getListValue(nextValue(), separator);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/ValueList.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see StringTokenizer and stored as a java.util.List.
* List methods, this class provides the same value casting mechanisms as
* {@link Configuration} and {@link ConfigurationEvent}. These have been extended to iterators through the
* {@link #valueIterator()} method.
*
* @author Nicolas Rinaudo
*/
public class ValueList extends ArrayListValueList initialised with the specified data.
* @param data data contained by the list.
* @param separator string used to separate data in tokens.
*/
public ValueList(String data, String separator) {
StringTokenizer tokenizer = new StringTokenizer(data, separator);
while(tokenizer.hasMoreTokens()) {
add(tokenizer.nextToken());
}
}
/**
* Returns the value found at the specified index of the list as a string.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as a string.
*/
public String valueAt(int index) {
return get(index);
}
/**
* Returns the value found at the specified index of the list as an integer.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as an integer.
* @throws NumberFormatException if the value cannot be cast to an integer.
*/
public int integerValueAt(int index) {
return ConfigurationSection.getIntegerValue(valueAt(index));
}
/**
* Returns the value found at the specified index of the list as a float.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as a float.
* @throws NumberFormatException if the value cannot be cast to a float.
*/
public float floatValueAt(int index) {
return ConfigurationSection.getFloatValue(valueAt(index));
}
/**
* Returns the value found at the specified index of the list as a double.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as a double.
* @throws NumberFormatException if the value cannot be cast to a double.
*/
public double doubleValueAt(int index) {
return ConfigurationSection.getDoubleValue(valueAt(index));
}
/**
* Returns the value found at the specified index of the list as a long.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as a long.
* @throws NumberFormatException if the value cannot be cast to a long.
*/
public long longValueAt(int index) {
return ConfigurationSection.getLongValue(valueAt(index));
}
/**
* Returns the value found at the specified index of the list as an boolean.
* @param index index of the value to retrieve.
* @return the value found at the specified index of the list as an boolean.
*/
public boolean booleanValueAt(int index) {
return ConfigurationSection.getBooleanValue(valueAt(index));
}
/**
* Returns the value found at the specified index of the list as a {@link ValueList}.
* @param index index of the value to retrieve.
* @param separator string used to split the value into tokens.
* @return the value found at the specified index of the list as a {@link ValueList}.
*/
public ValueList listValueAt(int index, String separator) {
return ConfigurationSection.getListValue(valueAt(index), separator);
}
/**
* Returns a {@link ValueIterator} on the list.
* @return a {@link ValueIterator} on the list.
*/
public ValueIterator valueIterator() {
return new ValueIterator(iterator());
}
/**
* Returns a string representation of the specified list.
* @param data values to represent as a string.
* @param separator string used to separate one element from the other.
* @return a string representation of the specified list.
*/
public static String toString(List> data, String separator) {
StringBuilder buffer = new StringBuilder();
Iterator> values = data.iterator();
// Deals with the first value separately.
if (values.hasNext()) {
buffer.append(values.next().toString());
}
// All subsequent values will be concatenated after a separator.
while (values.hasNext()) {
buffer.append(separator);
buffer.append(values.next().toString());
}
// Returns the final value.
return buffer.toString();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/WriterConfigurationException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see WriterConfigurationException subclasses {@link ConfigurationException}, it
* inherits his capacity to wrap other exceptions.
* @author Nicolas Rinaudo
*/
public class WriterConfigurationException extends ConfigurationException {
/**
* Creates a new writer configuration exception.
* @param message the error message.
*/
public WriterConfigurationException(String message) {
super(message);
}
/**
* Creates a new writer configuration exception wrapping an existing exception.
* WriterConfigurationException.
*
* @param cause the exception to be wrapped in a WriterConfigurationException.
*/
public WriterConfigurationException(Throwable cause) {
super(cause);
}
/**
* Creates a new writer configuration exception from an existing exception.
* WriterConfigurationException.
*/
public WriterConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/XmlConfigurationReader.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see XmlConfigurationReader is fairly simple:
*
*
*
* prefs, but this isn't enforced. It
* will be excluded from section names.
*
* <prefs>
* <some>
* Random CDATA
* <section>
* <var1>value1</var1>
* <var2>value2</var2>
* </section>
* </some>
* </prefs>
*
* This will be interpreted as follows:
*
*
*
* @author Nicolas Rinaudo
* @see XmlConfigurationWriter
*/
public class XmlConfigurationReader extends DefaultHandler implements ConfigurationReader {
/** Factory used to create {@link XmlConfigurationReader} instances. */
public static final ConfigurationReaderFactoryRandom CDATA will be ignored.some.section.var1 will be created with a value of value1.some.section.var2 will be created with a value of value2.in a passes build messages to builder.
* @param in input stream from which to read the configuration data.
* @param builder object to notify of build events.
* @throws IOException if an I/O error occurs.
* @throws ConfigurationFormatException if a configuration file format occurs.
* @throws ConfigurationException if a non-specific error occurs.
*/
public void read(Reader in, ConfigurationBuilder builder) throws IOException, ConfigurationException {
this.builder = builder;
locator = null;
try {
SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(in), this);
} catch (ParserConfigurationException e) {
throw new ConfigurationException("Failed to create a SAX parser", e);
} catch (SAXParseException e) {
throw new ConfigurationFormatException(e.getMessage(), e.getLineNumber(), e.getColumnNumber());
} catch (SAXException e) {
throw new ConfigurationFormatException(e.getException() == null ? e : e.getException());
}
}
// - XML handling --------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void characters(char[] ch, int start, int length) {buffer.append(ch, start, length);}
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
depth++;
if (depth == 1) {
return;
}
if (itemName != null) {
try {
builder.startSection(itemName);
} catch(Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
buffer.setLength(0);
itemName = qName;
isVariable = true;
}
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
depth--;
if (depth == 0) {
return;
}
// If the current element doesn't have subsections, considers it to be a variable.
if(isVariable) {
String value = buffer.toString().trim();
// Ignores empty values, otherwise notifies the builder of a new variable.
if(!value.isEmpty()) {
try {
builder.addVariable(qName, value);
} catch(Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
}
// The current element is a container, closes it.
else {
try {
builder.endSection(qName);
} catch(Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
isVariable = false;
itemName = null;
}
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void startDocument() throws SAXException {
try {
builder.startConfiguration();
} catch(Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void endDocument() throws SAXException {
try {
builder.endConfiguration();
} catch(Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
/**
* This method is public as an implementation side effect and should never be called directly.
*/
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/XmlConfigurationWriter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see startElement call). */
private final Attributes emptyAttributes = new AttributesImpl();
/** Root element name. */
protected final String rootElementName;
static {
FACTORY = new ConfigurationWriterFactoryIOException that might have occurred.
*/
public void startSection(String name) throws ConfigurationException {
startElement(name);
}
/**
* Ends a configuration section.
* @param name name of the closed section.
* @throws ConfigurationException as a wrapper for any IOException that might have occurred.
*/
public void endSection(String name) throws ConfigurationException {
endElement(name);
}
/**
* Creates a new variable in the current section.
* @param name name of the new variable.
* @param value value of the new variable.
* @throws ConfigurationException as a wrapper for any IOException that might have occurred.
*/
public void addVariable(String name, String value) throws ConfigurationException {
char[] data;
try {
startElement(name);
data = value.toCharArray();
out.characters(data, 0, data.length);
endElement(name);
} catch(SAXException e) {
throw new ConfigurationException(e);
}
}
/**
* Writes the XML header.
* @throws ConfigurationException as a wrapper for any exception that might have occurred.
*/
public void startConfiguration() throws ConfigurationException {
try {
out.startDocument();
startElement(rootElementName);
} catch(SAXException e) {
throw new ConfigurationException(e);
}
}
/**
* Writes the XML footer.
* @throws ConfigurationException as a wrapper for any IOException that might have occurred.
*/
public void endConfiguration() throws ConfigurationException {
try {
endElement(rootElementName);
out.endDocument();
} catch(SAXException e) {
throw new ConfigurationException(e);
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/conf/package-info.java
================================================
/**
* Provides classes to deal with software configuration.
* Configuration variables
* section.subsection.name where:
*
*
* section and subsection are both sections.name is the variable's name.
*
* Loading and storing configuration
* com.mucommander.commons.conf package offers various ways of loading and storing configuration.
* The most obvious way is by using the {@link com.mucommander.commons.conf.Configuration#read(java.io.Reader) read} and
* {@link com.mucommander.commons.conf.Configuration#write(java.io.Writer)} methods, but this has the disadvantage
* of forcing application writers to manage streams themselves.
* The preferred method is to create a dedicated {@link com.mucommander.commons.conf.ConfigurationSource} class and
* register it through {@link com.mucommander.commons.conf.Configuration#setSource(ConfigurationSource) setSource}.
* This allows an instance of {@link com.mucommander.commons.conf.Configuration} to know how to read from and write to
* its configuration file (or socket or any other medium that provides input and output streams).
*
* Changing the default configuration format
*
*
*
* Listening to the configuration
*
*
*/
package com.mucommander.commons.conf;
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractArchiveEntryFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractArchiveEntryFile represents a file entry inside an archive.
* An AbstractArchiveEntryFile is always associated with an {@link ArchiveEntry} object which contains
* information about the entry (name, size, date, ...) and with an {@link AbstractArchiveFile} which acts as an entry
* repository and provides operations such as listing a directory entry's files, adding or removing entries
* (if the archive is writable), etc...
*
* AbstractArchiveEntryFile implements {@link com.mucommander.commons.file.AbstractFile} by delegating methods to
* the ArchiveEntry and AbstractArchiveFile instances.
* AbstractArchiveEntryFile is agnostic to the actual archive format. In other words, there is no need to
* extend this class for a particular archive format, ArchiveEntry and AbstractArchiveFile
* provide a generic framework that isolates from the archive format's specifics.
*
*
*
* @see AbstractArchiveFile
* @see ArchiveEntry
* @author Maxence Bernard
*/
public abstract class AbstractArchiveEntryFile extends AbstractFile {
/** The archive file that contains this entry */
final AbstractArchiveFile archiveFile;
/** This entry file's parent, can be the archive file itself if this entry is located at the top level */
protected AbstractFile parent;
/** The ArchiveEntry object that contains information about this entry */
final protected ArchiveEntry entry;
/**
* Creates a new AbstractArchiveEntryFile.
*
* @param url the FileURL instance that represents this file's location
* @param archiveFile the AbstractArchiveFile instance that contains this entry
* @param entry the ArchiveEntry object that contains information about this entry
*/
protected AbstractArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {
super(url);
this.archiveFile = archiveFile;
this.entry = entry;
}
/**
* Returns the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...).
*
* @return the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...)
*/
public ArchiveEntry getEntry() {
return entry;
}
/**
* Returns the {@link AbstractArchiveFile} that contains the entry represented by this file.
*
* @return the AbstractArchiveFile that contains the entry represented by this file
*/
AbstractArchiveFile getArchiveFile() {
return archiveFile;
}
/**
* Returns the relative path of this entry, with respect to the archive file. The path separator of the returned
* path is the one returned by {@link #getSeparator()}. As a relative path, the returned path does not start
* with a separator character.
*
* @return the relative path of this entry, with respect to the archive file.
*/
private String getRelativeEntryPath() {
String path = entry.getPath();
// Replace all occurrences of the entry's separator by the archive file's separator, only if the separator is
// not "/" (i.e. the entry path separator).
String separator = getSeparator();
if (!separator.equals("/")) {
path = path.replace("/", separator);
}
return path;
}
/////////////////////////////////
// AbstractFile implementation //
/////////////////////////////////
@Override
public long getLastModifiedDate() {
return entry.getLastModifiedDate();
}
@Override
public long getSize() {
return entry.getSize();
}
@Override
public boolean isDirectory() {
return entry.isDirectory();
}
@Override
public boolean isArchive() {
// Archive entries files may be wrapped by archive files but they are not archive files per se
return false;
}
@Override
public AbstractFile[] ls() throws IOException {
return archiveFile.ls(this, null, null);
}
@Override
public AbstractFile[] ls(FilenameFilter filter) throws IOException {
return archiveFile.ls(this, filter, null);
}
@Override
public AbstractFile[] ls(FileFilter filter) throws IOException {
return archiveFile.ls(this, null, filter);
}
@Override
public AbstractFile getParent() {
return parent;
}
@Override
public void setParent(AbstractFile parent) {
this.parent = parent;
}
/**
* Returns true if this entry exists within the archive file.
*
* @return true if this entry exists within the archive file
*/
@Override
public boolean exists() {
return entry.exists();
}
@Override
public FilePermissions getPermissions() {
// Return the entry's permissions
return entry.getPermissions();
}
@Override
public void changePermission(int access, int permission, boolean enabled) throws IOException {
changePermissions(ByteUtils.setBit(getPermissions().getIntValue(), (permission << (access*3)), enabled));
}
@Override
public String getOwner() {
return entry.getOwner();
}
@Override
public boolean canGetOwner() {
return entry.getOwner()!=null;
}
@Override
public String getGroup() {
return entry.getGroup();
}
@Override
public boolean canGetGroup() {
return entry.getGroup()!=null;
}
/**
* Always returns false.
*/
@Override
public boolean isSymlink() {
return false;
}
/**
* Always returns false.
*/
@Override
public boolean isSystem() {
return false;
}
/**
* Delegates to the archive file's {@link AbstractArchiveFile#getFreeSpace()} method.
*
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if the underlying archive file does not support
* {@link FileOperation#GET_FREE_SPACE} operations.
*/
@Override
public long getFreeSpace() throws IOException {
return archiveFile.getFreeSpace();
}
/**
* Delegates to the archive file's {@link AbstractArchiveFile#getTotalSpace()} method.
*
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if the underlying archive file does not support
* {@link FileOperation#GET_TOTAL_SPACE} operations.
*/
@Override
public long getTotalSpace() throws IOException {
return archiveFile.getTotalSpace();
}
/**
* Delegates to the archive file's {@link AbstractArchiveFile#getEntryInputStream(ArchiveEntry,ArchiveEntryIterator)}}
* method.
*
* @throws UnsupportedFileOperationException if the underlying archive file does not support
* {@link FileOperation#READ_FILE} operations.
*/
@Override
public InputStream getInputStream() throws IOException {
return archiveFile.getEntryInputStream(entry, null);
}
/**
* Always throws an {@link UnsupportedFileOperationException}: append is not available for archive entries.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);
}
/**
* Always throws an {@link UnsupportedFileOperationException}: random read access is not available for archive
* entries.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);
}
/**
* Always throws an {@link UnsupportedFileOperationException}: random write access is not available for archive
* entries.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);
}
/**
* Always throws an {@link UnsupportedFileOperationException} when called.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {
// TODO: we could consider adding remote copy support to RWArchiveEntryFile
throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);
}
/**
* Always throws an {@link UnsupportedFileOperationException} when called.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {
// TODO: we could consider adding renaming support to RWArchiveEntryFile
throw new UnsupportedFileOperationException(FileOperation.RENAME);
}
/**
* Returns the same ArchiveEntry instance as {@link #getEntry()}.
*/
@Override
public Object getUnderlyingFileObject() {
return entry;
}
////////////////////////
// Overridden methods //
////////////////////////
/**
* This method is overridden to return the separator of the {@link #getArchiveFile() archive file} that contains
* this entry.
*
* @return the separator of the archive file that contains this entry
*/
@Override
public String getSeparator() {
return archiveFile.getSeparator();
}
/**
* This method is overridden to use the archive file's absolute path as the base path of this entry file.
*/
@Override
public String getAbsolutePath() {
// Use the archive file's absolute path and append the entry's relative path to it
return archiveFile.getAbsolutePath(true)+getRelativeEntryPath();
}
/**
* This method is overridden to use the archive file's canonical path as the base path of this entry file.
*/
@Override
public String getCanonicalPath() {
// Use the archive file's canonical path and append the entry's relative path to it
return archiveFile.getCanonicalPath(true)+getRelativeEntryPath();
}
/**
* This method is overridden to return the archive's root folder.
*/
@Override
public AbstractFile getRoot() {
return archiveFile.getRoot();
}
/**
* This method is overridden to blindly return false, an archive entry cannot be a root folder.
*
* @return false, always
*/
@Override
public boolean isRoot() {
return false;
}
/**
* This method is overridden to return the archive's volume folder.
*/
@Override
public AbstractFile getVolume() {
return archiveFile.getVolume();
}
@Override
@UnsupportedFileOperation
public short getReplication() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);
}
@Override
@UnsupportedFileOperation
public long getBlocksize() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);
}
@Override
@UnsupportedFileOperation
public void changeReplication(short replication) throws IOException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractArchiveFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractArchiveFile is the superclass of all archive files. It allows archive file to be browsed as if
* they were regular directories, independently of the underlying protocol used to access the actual file.
* AbstractArchiveFile extends {@link ProxyFile} to delegate the AbstractFile
* implementation to the actual archive file and overrides some methods to provide the added functionality.
* There are two kinds of AbstractArchiveFile, both of which extend this class:
*
*
* When implementing a new archive file/format, either AbstractROArchiveFile or AbstractRWArchiveFile
* should be subclassed, but not this class.
*
* ls() methods is called to list the archive's contents,
* {@link #getEntryIterator()} is called to retrieve a list of *all* the entries contained by the archive, not only the
* ones at the top level but also the ones nested one of several levels below. Using this list of entries, it creates
* a tree to map the structure of the archive and list the content of any particular directory within the archive.
* This tree is recreated (getEntryIterator() is called again) only if the archive file has changed, i.e.
* if its date has changed since the tree was created.
*
* ls() are {@link AbstractArchiveEntryFile} instances which use an {@link ArchiveEntry}
* object to retrieve the entry's attributes. In turn, these AbstractArchiveEntryFile instances query the
* associated AbstractArchiveFile to list their content.
*
From an implementation perspective, one only needs to deal with {@link ArchiveEntry} instances, all the nuts
* and bolts are taken care of by this class.
*
* AbstractArchiveFile may or may not actually be an archive:
* {@link #isArchive()} returns true only if the file currently exists and is not a directory. The value
* returned by {@link #isArchive()} may change over time as the file is modified. When an
* AbstractArchiveFile is not currently an archive, it acts just as a 'normal' file and delegates
* ls() methods to the underlying {@link AbstractFile}
*
* @see com.mucommander.commons.file.FileFactory
* @see com.mucommander.commons.file.ArchiveFormatProvider
* @see com.mucommander.commons.file.ArchiveEntry
* @see AbstractArchiveEntryFile
* @see com.mucommander.commons.file.archiver.Archiver
* @author Maxence Bernard
*/
public abstract class AbstractArchiveFile extends ProxyFile {
private static Logger logger;
/** Archive entries tree */
private ArchiveEntryTree entryTreeRoot;
/** Date this file had when the entries tree was created. Used to detect if the archive file has changed and entries
* need to be reloaded */
private long entryTreeDate;
/** The password to use for a password-protected archive */
protected String password;
/** Caches {@link AbstractArchiveEntryFile} instances so that there is only one AbstractArchiveEntryFile
* corresponding to the same entry at any given time, to avoid attribute inconsistencies. The key is the
* corresponding ArchiveEntry. */
private WeakHashMapnull if the tree hasn't been intialized yet.
*
* @return the ArchiveEntryTree instance corresponding to the root of the archive entry tree
*/
ArchiveEntryTree getArchiveEntryTree() {
return entryTreeRoot;
}
/**
* Returns the contents of the specified folder entry.
*
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
protected AbstractFile[] ls(AbstractArchiveEntryFile entryFile, FilenameFilter filenameFilter, FileFilter fileFilter) throws IOException {
// Make sure the entries tree is created and up-to-date
checkEntriesTree();
if (!entryFile.isBrowsable()) {
throw new IOException();
}
DefaultMutableTreeNode matchNode = entryTreeRoot.findEntryNode(entryFile.getEntry().getPath());
if (matchNode == null) {
throw new IOException();
}
return ls(matchNode, entryFile, filenameFilter, fileFilter);
}
/**
* Returns the contents (direct children) of the specified tree node.
*
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
private AbstractFile[] ls(DefaultMutableTreeNode treeNode, AbstractFile parentFile, FilenameFilter filenameFilter, FileFilter fileFilter) throws IOException {
AbstractFile[] files;
int nbChildren = treeNode.getChildCount();
// No FilenameFilter, create entry files and store them directly into an array
if (filenameFilter == null) {
files = new AbstractFile[nbChildren];
for (int c=0; c < nbChildren; c++) {
files[c] = getArchiveEntryFile((ArchiveEntry)(((DefaultMutableTreeNode)treeNode.getChildAt(c)).getUserObject()), parentFile);
}
}
// Use provided FilenameFilter and temporarily store created entry files that match the filter in a Vector
else {
Listls() is called. It will not be called anymore,
* unless the file's date has changed since the last time one of the ls() methods was called.
*
* @return an iterator of {@link ArchiveEntry} that iterates through all the entries of this archive
* @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or
* because of an I/O error
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
public abstract ArchiveEntryIterator getEntryIterator() throws IOException;
/**
* Returns an InputStream to read from the given archive entry. The specified {@link ArchiveEntry}
* instance must be one of the entries that were returned by the {@link ArchiveEntryIterator} returned by
* {@link #getEntryIterator()}.
*
* @param entry the archive entry to read
* @param entryIterator the iterator that is used to iterate through entries by the caller (if any). This parameter
* may be null, but when it is known, specifying may improve the performance of this method
* by an order of magnitude.
* @return an InputStream to read from the given archive entry
* @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or
* because of an I/O error, or if the given entry wasn't found in the archive
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
public abstract InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException;
/**
* Returns true if this archive file is writable, i.e. is capable of adding and deleting entries from
* the underlying archive file.
*
* false and
* true. This method may be overridden by AbstractRWArchiveFile implementations if write
* access is only available under certain conditions, for example if it requires random write access to the
* proxied archive file (which may not always be available).
* Therefore, this method should be used to test if an AbstractArchiveFile is writable, rather than
* testing if it is an instance of AbstractRWArchiveFile.
*
* @return true if this archive is writable, i.e. is capable of adding and deleting entries from
* the underlying archive file.
*/
public abstract boolean isWritable();
@Override
public boolean isArchive() {
return exists() && !isDirectory();
}
/**
* This method is overridden to list and return the topmost entries contained by this archive.
* The returned files are {@link AbstractArchiveEntryFile} instances.
*
* @return the topmost entries contained by this archive
* @throws IOException if the archive entries could not be listed
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
@Override
public AbstractFile[] ls() throws IOException {
// Delegate to the ancestor if this file isn't actually an archive
if (!isArchive())
return super.ls();
// Make sure the entries tree is created and up-to-date
checkEntriesTree();
return ls(entryTreeRoot, this, null, null);
}
/**
* This method is overridden to list and return the topmost entries contained by this archive, filtering out
* the ones that do not match the specified {@link FilenameFilter}. The returned files are {@link AbstractArchiveEntryFile}
* instances.
*
* @param filter the FilenameFilter to be used to filter files out from the list, may be null
* @return the topmost entries contained by this archive
* @throws IOException if the archive entries could not be listed
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
@Override
public AbstractFile[] ls(FilenameFilter filter) throws IOException {
// Delegate to the ancestor if this file isn't actually an archive
if (!isArchive())
return super.ls(filter);
// Make sure the entries tree is created and up-to-date
checkEntriesTree();
return ls(entryTreeRoot, this, filter, null);
}
/**
* This method is overridden to list and return the topmost entries contained by this archive, filtering out
* the ones that do not match the specified {@link FileFilter}. The returned files are {@link AbstractArchiveEntryFile} instances.
*
* @param filter the FilenameFilter to be used to filter files out from the list, may be null
* @return the topmost entries contained by this archive
* @throws IOException if the archive entries could not be listed
* @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the
* underlying file protocol.
*/
@Override
public AbstractFile[] ls(FileFilter filter) throws IOException {
// Delegate to the ancestor if this file isn't actually an archive
if (!isArchive()) {
return super.ls(filter);
}
// Make sure the entries tree is created and up-to-date
checkEntriesTree();
return ls(entryTreeRoot, this, null, filter);
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// Note: do not override #isDirectory() to always return true, as AbstractArchiveFile instances may be created when
// the file does not exist yet, and then be mkdir(): in that case, the file will be a directory and not an archive.
private static Logger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(AbstractArchiveFile.class);
}
return logger;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractFile is the superclass of all files.
*
* getFile
* methods should be used to get a file instance from a path or {@link FileURL} location.
*
* @see com.mucommander.commons.file.FileFactory
* @see com.mucommander.commons.file.impl.ProxyFile
* @author Maxence Bernard
*/
public abstract class AbstractFile implements FileAttributes, PermissionTypes, PermissionAccesses {
/** URL representing this file */
protected final FileURL fileURL;
/** Default path separator */
protected final static String DEFAULT_SEPARATOR = "/";
/** Size of the read/write buffer */
// Note: raising buffer size from 8192 to 65536 makes a huge difference in SFTP read transfer rates but beyond
// 65536, no more gain (not sure why).
protected final static int IO_BUFFER_SIZE = 65536;
/**
* Used for method getPushBackInputStream()
*/
private MuPushbackInputStream pushbackInputStream;
/**
* Creates a new file instance with the given URL.
*
* @param url the FileURL instance that represents this file's location
*/
protected AbstractFile(FileURL url) {
this.fileURL = url;
}
/**
* Returns the {@link FileURL} instance that represents this file's location.
*
* @return the FileURL instance that represents this file's location
*/
public FileURL getURL() {
return fileURL;
}
/**
* Creates and returns a java.net.URL referring to the same location as the {@link FileURL} associated
* with this AbstractFile.
* The java.net.URL is created from the string representation of this file's FileURL.
* Thus, any credentials this FileURL contains are preserved, but properties are lost.
*
* URL uses this {@link AbstractFile} to access the associated resource, via the
* underlying URLConnection which delegates to this class.
*
* java.net.URL.
*
* @return a java.net.URL referring to the same location as this FileURL
* @throws java.net.MalformedURLException if the java.net.URL could not parse the location of this FileURL
*/
public URL getJavaNetURL() throws MalformedURLException {
return new URL(null, getURL().toString(true), new CompatURLStreamHandler(this));
}
/**
* Returns this file's name.
*
* FileURL
* as returned by {@link FileURL#getFilename()}. If the filename is null (e.g. http://google.com), the
* FileURL's host will be returned instead. If the host is null (e.g. smb://), an empty
* String will be returned. Thus, the returned name will never be null.
*
* null if this file's name doesn't have an extension.
*
*
* - it contains at least one . character
* - the last . is not the last character of the filename
* - the last . is not the first character of the filename
*
* @return this file's extension, null if this file's name doesn't have an extension
*/
public String getExtension() {
return getExtension(getName());
}
/**
* Returns the absolute path to this file:
*
*
* AbstractFile representing the canonical path of this file, or this if the
* absolute and canonical path of this file are identical.
* Note that the returned file may or may not exist, for example if this file is a symlink to a file that doesn't
* exist.
*
* @return an AbstractFile representing the canonical path of this file, or this if the absolute and canonical
* path of this file are identical.
*/
public AbstractFile getCanonicalFile() {
String canonicalPath = getCanonicalPath(false);
if (canonicalPath.equals(getAbsolutePath(false))) {
return this;
}
try {
FileURL canonicalURL = FileURL.getFileURL(canonicalPath);
canonicalURL.setCredentials(fileURL.getCredentials());
return FileFactory.getFile(canonicalURL);
} catch (IOException e) {
return this;
}
}
/**
* Returns the path separator used by this file.
*
* true if this file is hidden.
*
* true if this
* file's name starts with '.'. This method should be overridden if the underlying filesystem has a notion
* of hidden files.
*
* @return true if this file is hidden
*/
public boolean isHidden() {
return getName().startsWith(".");
}
/**
* Returns true if this file is executable.
*
* @return true if this file is executable
*/
public boolean isExecutable() {
if (OsFamily.WINDOWS.isCurrent()) {
if (isDirectory()) {
return false;
}
String ext = getExtension();
return ext != null && (ext.equalsIgnoreCase("exe") || ext.equalsIgnoreCase("com") || ext.equalsIgnoreCase("bat") || ext.equalsIgnoreCase("cmd"));
}
return !isDirectory() && getPermissions().getBitValue(USER_ACCESS, EXECUTE_PERMISSION);
}
/**
* Return true if the application can read this file.
* TODO: need to be overridden with a correct check for each file type.
*
* @return true if the application can read this file.
* **/
public boolean canRead() {
return true;
}
/**
* Returns the root folder of this file, i.e. the top-level parent folder that has no parent folder. The returned
* folder necessarily contains this file, directly or indirectly. If this file already is a root folder, the same
* file will be returned.
* /.
*
* @return the root folder that contains this file
*/
public AbstractFile getRoot() {
FileURL rootURL = (FileURL)getURL().clone();
rootURL.setPath("/");
return FileFactory.getFile(rootURL);
}
/**
* Returns true if this file is a root folder.
* true if this file's URL path is /.
*
* @return true if this file is a root folder
*/
public boolean isRoot() {
return getURL().getPath().equals("/");
}
/**
* Returns the volume on which this file is located, or this if this file is itself a volume.
* The returned file may never be null. Furthermore, the returned file may not always
* {@link #exists() exist}, for instance if the returned volume corresponds to a removable drive that's currently
* unavailable. If the returned file does exist, it must always be a {@link #isDirectory() directory}.
* In other words, archive files may not be considered as volumes.
* InputStream to read this file's contents, starting at the specified offset (in bytes).
* A java.io.IOException is thrown if the file doesn't exist.
*
* InputStream implementations is very slow as it causes the bytes to be read and discarded.
* For this reason, file implementations that do not provide random read access may want to override this method
* if a more efficient implementation can be provided.
*
* @param offset the offset in bytes from the beginning of the file, must be not negative
* @throws IOException if this file cannot be read or is a folder.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
* @return an InputStream to read this file's contents, skipping the specified number of bytes
*/
public InputStream getInputStream(long offset) throws IOException {
// Use random access input stream when available
if (isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {
RandomAccessInputStream rais = getRandomAccessInputStream();
rais.seek(offset);
return rais;
}
InputStream in = getInputStream();
// Skip exactly the specified number of bytes
StreamUtils.skipFully(in, offset);
return in;
}
/**
* Copies the contents of the given InputStream to this file, appending or overwriting the file
* if it exists. It is noteworthy that the provided InputStream will not be closed by this method.
*
* InputStream and use it to write the file.
* For this reason, it is recommended to use this method to write a file, rather than copying streams manually using
* {@link #getOutputStream()}
*
* length parameter is optional. Setting its value help certain protocols which need to know
* the length in advance. This is the case for instance for some HTTP-based protocols like Amazon S3, which require
* the Content-Length header to be set in the request. Callers should thus set the length if it is
* known.
*
* -1 if unknown.
* @throws FileTransferException if something went wrong while reading from the InputStream or writing to this file
*/
public void copyStream(InputStream in, boolean append, long length) throws FileTransferException {
OutputStream out;
try {
out = append ? getAppendOutputStream() : getOutputStream();
} catch (UnsupportedFileOperationException e) {
throw new FileTransferException(FileTransferException.UNSUPPORTED_OPERATION, e);
} catch (IOException e) {
throw new FileTransferException(FileTransferException.OPENING_DESTINATION, e);
}
try {
StreamUtils.copyStream(in, out, IO_BUFFER_SIZE);
} finally {
// Close stream even if copyStream() threw an IOException
try {
out.close();
} catch(IOException e) {
throw new FileTransferException(FileTransferException.CLOSING_DESTINATION, e);
}
}
}
/**
* Copies this file to a specified destination file, overwriting the destination if it exists. If this file is a
* directory, any file or directory it contains will also be copied.
*
*
*
*
*
* In that case, no clean up is performed if an error occurs in the midst of a transfer: files that have been copied
* (even partially) are left in the destination.
* It is also worth noting that symbolic links are not copied to the destination when encountered: neither the link
* nor the linked file is copied
*
* @param destFile the destination file to copy this file to
* @throws IOException in any of the error cases listed above
*/
public final void copyTo(AbstractFile destFile) throws IOException {
// First, try to perform a remote copy of the file if the operation is supported
if (isFileOperationSupported(FileOperation.COPY_REMOTELY)) {
try {
copyRemotelyTo(destFile);
return; // Operation was a success, all done.
} catch (IOException ignore) {}
}
// Fall back to copying the file manually
checkCopyPrerequisites(destFile, false);
// Copy the file and its contents if the file is a directory
copyRecursively(this, destFile);
}
/**
* Moves this file to a specified destination file, overwriting the destination if it exists. If this file is a
* directory, any file or directory it contains will also be moved.
* After normal completion, this file will not exist anymore: {@link #exists()} will return false.
*
*
*
*
*
* In that case, deletion of the source occurs only after all files have been successfully transferred.
* No clean up is performed if an error occurs in the midst of a transfer: files that have been copied
* (even partially) are left in the destination.
* It is also worth noting that symbolic links are not moved to the destination when encountered: neither the link
* nor the linked file is moved, and the symlink file is deleted.
*
* @param destFile the destination file to move this file to
* @throws IOException in any of the error cases listed above
*/
public final void moveTo(AbstractFile destFile) throws IOException {
// First, try to rename the file if the operation is supported
if (isFileOperationSupported(FileOperation.RENAME)) {
try {
renameTo(destFile);
// Rename was a success, all done.
return;
} catch (IOException ignore) {}
}
// Fall back to moving the file manually
copyTo(destFile);
// Delete the source file and its contents now that it has been copied OK.
// Note that the file won't be deleted if copyTo() failed (threw an IOException)
try {
deleteRecursively();
} catch(IOException e) {
throw new FileTransferException(FileTransferException.DELETING_SOURCE);
}
}
/**
* Creates this file as an empty, non-directory file. This method will fail (throw an IOException)
* if this file already exists. Note that this method may not always yield a zero-byte file (see below).
*
* true.
*
* @param filter the FileFilter to be used to filter files out from the list, may be null
* @return the children files that this file contains
* @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public AbstractFile[] ls(FileFilter filter) throws IOException {
return filter == null ? ls() : filter.filter(ls());
}
/**
* Returns the children files that this file contains, filtering out files that do not match the specified FilenameFilter.
* For this operation to be successful, this file must be 'browsable', i.e. {@link #isBrowsable()} must return
* true.
*
* null
* @return the children files that this file contains
* @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public AbstractFile[] ls(FilenameFilter filter) throws IOException {
return filter == null ? ls() : filter.filter(ls());
}
/**
* Changes this file's permissions to the specified permissions int.
* The permissions int should be constructed using the permission types and accesses defined in
* {@link com.mucommander.commons.file.PermissionTypes} and {@link com.mucommander.commons.file.PermissionAccesses}.
*
*
*
*
*
*
*
* @return a string representation of this file's permissions
*/
public String getPermissionsString() {
FilePermissions permissions = getPermissions();
if (permissions == null) {
return isSymlink() ? "l???" : isDirectory() ? "d???" : "-???";
}
int supportedPerms = permissions.getMask().getIntValue();
StringBuilder sb = new StringBuilder();
sb.append(isSymlink() ? 'l' : isDirectory() ? 'd' : '-');
int perms = permissions.getIntValue();
int bitShift = USER_ACCESS *3;
// Permissions go by triplets (rwx), there are 3 of them for respectively 'owner', 'group' and 'other' accesses.
// The first one ('owner') will always be displayed, regardless of the permission bit mask. 'Group' and 'other'
// will be displayed only if the permission mask contains information about them (at least one permission bit).
for (int a = USER_ACCESS; a >= OTHER_ACCESS; a--) {
if (a == USER_ACCESS || (supportedPerms & (7<d---, no matter
* what permission values the FilePermissions returned by {@link #getPermissions()} contains-rwxrwxrwxAbstractFile methods.
* true is returned, this doesn't ensure that the file operation will succeed:
* additional conditions may be required for the operation to succeed and the corresponding method may throw an
* IOException if those conditions are not met.
*
* @param op a file operation
* @return true if the specified file operation is supported by this filesystem.
* @see FileOperation
*/
public boolean isFileOperationSupported(FileOperation op) {
return isFileOperationSupported(op, getClass());
}
/**
* Returns true if this file is browsable. A file is considered browsable if it contains children files
* that can be retrieved by calling the ls() methods. Archive files will usually return
* true, as will directories (directories are always browsable).
*
* @return true if this file is browsable
*/
public final boolean isBrowsable() {
return isDirectory() || isArchive();
}
/**
* Returns the name of the file without its extension.
*
*
* - it contains at least one . character
* - the last . is not the last character of the filename
* - the last . is not the first character of the filename
* If this file has no extension, its full name is returned.
*
* @return this file's name, without its extension.
* @see #getName()
* @see #getExtension()
*/
public final String getNameWithoutExtension() {
String name = getName();
int position = name.lastIndexOf('.');
if (position <= 0 || position == name.length() - 1) {
return name;
}
return name.substring(0, position);
}
/**
* Shorthand for {@link #getAbsolutePath()}.
*
* @return the value returned by {@link #getAbsolutePath()}.
*/
public final String getPath() {
return getAbsolutePath();
}
/**
* Returns the absolute path to this file.
* A separator character will be appended to the returned path if true is passed.
*
* @param appendSeparator if true, a separator will be appended to the returned path
* @return the absolute path to this file
*/
public final String getAbsolutePath(boolean appendSeparator) {
String path = getAbsolutePath();
return appendSeparator?addTrailingSeparator(path): removeTrailingSeparator(path);
}
/**
* Returns the canonical path to this file, resolving any symbolic links or '..' and '.' occurrences.
* A separator character will be appended to the returned path if true is passed.
*
* @param appendSeparator if true, a separator will be appended to the returned path
* @return the canonical path to this file
*/
public final String getCanonicalPath(boolean appendSeparator) {
String path = getCanonicalPath();
return appendSeparator ? addTrailingSeparator(path) : removeTrailingSeparator(path);
}
/**
* Returns a child of this file, whose path is the concatenation of this file's path and the given relative path.
* Although this method does not enforce it, the specified path should be relative, i.e. should not start with
* a separator.
* An IOException may be thrown if the child file could not be instantiated but the returned file
* instance should never be null.
*
* @param relativePath the child's path, relative to this file's path
* @return an AbstractFile representing the requested child file, never null
* @throws IOException if the child file could not be instantiated
*/
public final AbstractFile getChild(String relativePath) throws IOException {
FileURL childURL = (FileURL)getURL().clone();
childURL.setPath(addTrailingSeparator(childURL.getPath()) + relativePath);
return FileFactory.getFile(childURL, true);
}
/**
* Convenience method that acts as {@link #getChild(String)} except that it does not throw {@link IOException} but
* returns null if the child could not be instantiated.
*
* @param relativePath the child's path, relative to this file's path
* @return an AbstractFile representing the requested child file, null if it could not be instantiated
*/
public final AbstractFile getChildSilently(String relativePath) {
try {
return getChild(relativePath);
} catch(IOException e) {
return null;
}
}
/**
* Returns a direct child of this file, whose path is the concatenation of this file's path and the given filename.
* An IOException will be thrown in any of the following cases:
*
*
* This method never returns null.
*
* IOException) if this file already exists. It may also fail because of an I/O error ;
* in this case, this method will not remove the parent directories it has created (if any).
*
* @throws IOException if this file already exists or if an I/O error occurred.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final void mkdirs() throws IOException {
AbstractFile parent = getParent();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
mkdir();
}
/**
* Convenience method that creates a file as a direct child of this directory.
* This method will fail if this file is not a directory.
*
* @param name name of the file to create
* @throws IOException if the file could not be created, either because the file already exists or for any
* other reason.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final void mkfile(String name) throws IOException {
getChild(name).mkfile();
}
/**
* Returns the immediate ancestor of this AbstractFile if it has one, this otherwise:
*
*
*
* @return the immediate ancestor of this ProxyFile, returns this
* AbstractFile if it has one, this otherwise
*/
public final AbstractFile getAncestor() {
if (this instanceof ProxyFile) {
return ((ProxyFile) this).getProxiedFile();
}
return this;
}
/**
* Returns the first ancestor of this file that is an instance of the given Class or of a subclass of it,
* or this if this instance's class matches those criteria. Returns null if this
* file has no such ancestor.
*
* Note that this method will always return this if AbstractFile.class is specified.
*
* @param abstractFileClass a Class corresponding to an AbstractFile subclass
* @return the first ancestor of this file that is an instance of the given Class or of a subclass of the given
* Class, or this if this instance's class matches those criteria. Returns null if this
* file has no such ancestor.
*/
public final this will be returned.
*
* @return returns the top-most ancestor of this file, this if this file has no ancestor
*/
public final AbstractFile getTopAncestor() {
AbstractFile topAncestor = this;
while (topAncestor.hasAncestor()) {
topAncestor = topAncestor.getAncestor();
}
return topAncestor;
}
/**
* Returns true if this AbstractFile has an ancestor, i.e. if this file is a
* {@link ProxyFile}, false otherwise.
*
* @return true if this AbstractFile has an ancestor, false otherwise.
*/
public final boolean hasAncestor() {
return this instanceof ProxyFile;
}
/**
* Returns true if this file is or has an ancestor (immediate or not) that is an instance of the given
* Class or of a subclass of the Class. Note that the specified must correspond to an
* AbstractFile subclass. Specifying any other Class will always yield to this method returning
* false. Also note that this method will always return true if
* AbstractFile.class is specified.
*
* @param abstractFileClass a Class corresponding to an AbstractFile subclass
* @return true if this file has an ancestor (immediate or not) that is an instance of the given Class
* or of a subclass of the given Class.
*/
public final boolean hasAncestor(Class extends AbstractFile> abstractFileClass) {
AbstractFile ancestor = this;
AbstractFile lastAncestor;
do {
if (abstractFileClass.isAssignableFrom(ancestor.getClass())) {
return true;
}
lastAncestor = ancestor;
ancestor = ancestor.getAncestor();
} while (lastAncestor != ancestor);
return false;
}
/**
* Returns true if this file is a parent folder of the given file, or if the two files are equal.
*
* @param file the AbstractFile to test
* @return true if this file is a parent folder of the given file, or if the two files are equal
*/
public final boolean isParentOf(AbstractFile file) {
return isBrowsable() && file.getCanonicalPath(true).startsWith(getCanonicalPath(true));
}
/**
* Convenience method that returns the parent {@link AbstractArchiveFile} that contains this file. If this file
* is an {@link AbstractArchiveFile} or an ancestor of {@link AbstractArchiveFile}, this is returned.
* If this file is neither contained by an archive nor is an archive, null is returned.
*
* null if the JVM is running on a headless environment.
*
* @param preferredResolution the preferred icon resolution
* @return an icon representing this file, null if the JVM is running on a headless environment
* @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()
* @see com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)
*/
public final Icon getIcon(Dimension preferredResolution) {
return FileFactory.getDefaultFileIconProvider().getFileIcon(this, preferredResolution);
}
/**
* Returns an icon representing this file, using the default {@link com.mucommander.commons.file.icon.FileIconProvider}
* registered in {@link FileFactory}. The default preferred resolution for the icon is 16x16 pixels.
* This method may return null if the JVM is running on a headless environment.
*
* @return an icon representing this file, null if the JVM is running on a headless environment
* @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()
* @see com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)
*/
public final Icon getIcon() {
// Note: the Dimension object is created here instead of returning a final static field, because creating
// a Dimension object triggers the AWT and Swing classes loading. Since these classes are not
// needed in a headless environment, we want them to be loaded only if strictly necessary.
return getIcon(new java.awt.Dimension(16, 16));
}
/**
* Returns a checksum of this file (also referred to as hash or digest) calculated by reading this
* file's contents and feeding the bytes to the given MessageDigest, until EOF is reached.
*
* MessageDigest after the checksum has been calculated.
*
* @param algorithm the algorithm to use for calculating the checksum
* @return this file's checksum, as an hexadecimal string
* @throws IOException if an I/O error occurred while calculating the checksum
* @throws NoSuchAlgorithmException if the specified algorithm does not correspond to any MessageDigest registered
* with the Java Cryptography Extension.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final String calculateChecksum(String algorithm) throws IOException, NoSuchAlgorithmException {
return calculateChecksum(MessageDigest.getInstance(algorithm));
}
/**
* Returns a checksum of this file (also referred to as hash or digest) calculated by reading this
* file's contents and feeding the bytes to the given MessageDigest, until EOF is reached.
*
* MessageDigest.
*
* MessageDigest after the checksum has been calculated.
*
* @param messageDigest the MessageDigest to use for calculating the checksum
* @return this file's checksum, as an hexadecimal string
* @throws IOException if an I/O error occurred while calculating the checksum
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final String calculateChecksum(MessageDigest messageDigest) throws IOException {
try (InputStream in = getInputStream()) {
return calculateChecksum(in, messageDigest);
}
}
/**
* Tests if the given path contains a trailing separator, and if not, adds one to the returned path.
* The separator used is the one returned by {@link #getSeparator()}.
*
* @param path the path for which to add a trailing separator
* @return the path with a trailing separator
*/
public final String addTrailingSeparator(String path) {
// Even though getAbsolutePath() is not supposed to return a trailing separator, root folders ('/', 'c:\' ...)
// are exceptions that's why we still have to test if path ends with a separator
String separator = getSeparator();
if (!path.endsWith(separator)) {
return path + separator;
}
return path;
}
/**
* Tests if the given path contains a trailing separator, and if it does, removes it from the returned path.
* The separator used is the one returned by {@link #getSeparator()}.
*
* @param path the path for which to remove the trailing separator
* @return the path free of a trailing separator
*/
protected final String removeTrailingSeparator(String path) {
// Remove trailing slash if path is not '/' or trailing backslash if path does not end with ':\'
// (Reminder: C: is C's current folder, while C:\ is C's root)
String separator = getSeparator();
if (path.endsWith(separator)
&& !((separator.equals("/") && path.length() == 1) || (separator.equals("\\") && path.charAt(path.length()-2)==':')))
path = path.substring(0, path.length()-1);
return path;
}
/**
* Checks the prerequisites of a copy (or move) operation.
* Throws a {@link FileTransferException} if any of the following conditions are true, does nothing otherwise:
*
*
*
* @param destFile the destination file to copy this file to
* @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another
* @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to
* know the reason.
*/
protected final void checkCopyPrerequisites(AbstractFile destFile, boolean allowCaseVariations) throws FileTransferException {
boolean isAllowedCaseVariation = false;
// Throw an exception of a specific kind if the source and destination files refer to the same file
boolean filesEqual = this.equalsCanonical(destFile);
if (filesEqual) {
// If case variations are allowed and the destination filename is a case variation of the source,
// do not throw an exception.
if (allowCaseVariations) {
String sourceFileName = getName();
String destFileName = destFile.getName();
if (sourceFileName.equalsIgnoreCase(destFileName) && !sourceFileName.equals(destFileName)) {
isAllowedCaseVariation = true;
}
}
if (!isAllowedCaseVariation) {
throw new FileTransferException(FileTransferException.SOURCE_AND_DESTINATION_IDENTICAL);
}
}
// Throw an exception if source is a parent of destination
if (!filesEqual && isParentOf(destFile)) { // Note: isParentOf(destFile) returns true if both files are equal
throw new FileTransferException(FileTransferException.SOURCE_PARENT_OF_DESTINATION);
}
// Throw an exception if the source file does not exist
if (!exists()) {
throw new FileTransferException(FileTransferException.FILE_NOT_FOUND);
}
}
/**
* Checks the prerequisites of a {@link #copyRemotelyTo(AbstractFile)} operation.
* This method starts by verifying the following requirements and throws an allowCaseVariations is true
* and the destination filename is a case variation of the sourceIOException if one of them
* isn't met:
*
*
* If all those requirements are met, {@link #checkCopyPrerequisites(AbstractFile, boolean)} is called with the
* destination file and allowDifferentHosts is trueallowCaseVariations flag to perform prerequisites verifications.
*
* @param destFile the destination file to copy this file to
* @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another
* @param allowDifferentHosts prevents throwing an exception if both files have the same host
* @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to
* know the reason.
* @see #checkCopyPrerequisites(AbstractFile, boolean)
*/
protected final void checkCopyRemotelyPrerequisites(AbstractFile destFile, boolean allowCaseVariations, boolean allowDifferentHosts) throws IOException {
if (!fileURL.schemeEquals(fileURL)
|| !destFile.getTopAncestor().getClass().equals(getTopAncestor().getClass())
|| (!allowDifferentHosts && !destFile.getURL().hostEquals(fileURL)))
throw new IOException();
checkCopyPrerequisites(destFile, allowCaseVariations);
}
/**
* Checks the prerequisites of a {@link #renameTo(AbstractFile)} operation.
* This method starts by verifying the following requirements and throws an IOException if one of them
* isn't met:
*
*
* If all those requirements are met, {@link #checkCopyPrerequisites(AbstractFile, boolean)} is called with the
* destination file and allowDifferentHosts is trueallowCaseVariations flag to perform further prerequisites verifications.
*
* @param destFile the destination file to copy this file to
* @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another
* @param allowDifferentHosts prevents throwing an exception if both files have the same host
* @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to
* know the reason.
* @see #checkCopyPrerequisites(AbstractFile, boolean)
*/
protected final void checkRenamePrerequisites(AbstractFile destFile, boolean allowCaseVariations, boolean allowDifferentHosts) throws IOException {
checkCopyRemotelyPrerequisites(destFile, allowCaseVariations, allowDifferentHosts);
}
/**
* Copies the source file to the destination one and recurses on directory contents.
* This method assumes that the destination file does not exists, this must be checked prior to calling this method.
* Symbolic links are skipped when encountered: neither the link nor the linked file are copied.
*
* @param sourceFile the file to copy
* @param destFile the destination file
* @throws FileTransferException if an error occurred while copying the file
*/
protected final void copyRecursively(AbstractFile sourceFile, AbstractFile destFile) throws FileTransferException {
if (sourceFile.isSymlink()) {
return;
}
if (sourceFile.isDirectory()) {
try {
destFile.mkdir();
} catch(IOException e) {
throw new FileTransferException(FileTransferException.WRITING_DESTINATION);
}
AbstractFile[] children;
try {
children = sourceFile.ls();
} catch(IOException e) {
throw new FileTransferException(FileTransferException.READING_SOURCE);
}
AbstractFile destChild;
for (AbstractFile child : children) {
try {
destChild = destFile.getDirectChild(child.getName());
} catch (IOException e) {
throw new FileTransferException(FileTransferException.OPENING_DESTINATION);
}
copyRecursively(child, destChild);
}
} else {
// try (InputStream in = sourceFile.getInputStream()) {
// destFile.copyStream(in, false, sourceFile.getSize());
// } catch (IOException e) {
// throw new FileTransferException(FileTransferException.OPENING_SOURCE);
// }
InputStream in;
try {
in = sourceFile.getInputStream();
} catch(IOException e) {
throw new FileTransferException(FileTransferException.OPENING_SOURCE);
}
try {
destFile.copyStream(in, false, sourceFile.getSize());
} finally {
// Close stream even if copyStream() threw an IOException
try {
in.close();
} catch (IOException e) {
throw new FileTransferException(FileTransferException.CLOSING_SOURCE);
}
}
}
}
/**
* Deletes the given file. If the file is a directory, enclosing files are deleted recursively.
* Symbolic links to directories are simply deleted, without deleting the contents of the linked directory.
*
* @param file the file to delete
* @throws IOException if an error occurred while deleting a file or listing a directory's contents
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
private void deleteRecursively(AbstractFile file) throws IOException {
if (file.isDirectory() && !file.isSymlink()) {
AbstractFile[] children = file.ls();
for (AbstractFile child : children) {
deleteRecursively(child);
}
}
file.delete();
}
/**
* Convenience method that calls {@link #changePermissions(int)} with the given permissions' int value.
*
* @param permissions new permissions for this file
* @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because
* of an I/O error.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final void changePermissions(FilePermissions permissions) throws IOException {
changePermissions(permissions.getIntValue());
}
/**
* This method is a shorthand for {@link #importPermissions(AbstractFile, FilePermissions)} called with
* {@link FilePermissions#DEFAULT_DIRECTORY_PERMISSIONS} if this file is a directory or
* {@link FilePermissions#DEFAULT_FILE_PERMISSIONS} if this file is a regular file.
*
* @param sourceFile the file from which to import permissions
* @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because
* of an I/O error.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
*/
public final void importPermissions(AbstractFile sourceFile) throws IOException {
importPermissions(sourceFile, isDirectory()
? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS
: FilePermissions.DEFAULT_FILE_PERMISSIONS);
}
/**
* Imports the given source file's permissions, overwriting this file's permissions. Only the bits that are
* supported by the source file (as reported by the permissions' mask) are preserved. Other bits are be
* set to those of the specified default permissions.
* See {@link SimpleFilePermissions#padPermissions(FilePermissions, FilePermissions)} for more information about
* permissions padding.
*
* @param sourceFile the file from which to import permissions
* @param defaultPermissions default permissions to use
* @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because
* of an I/O error.
* @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported
* or not implemented by the underlying filesystem.
* @see SimpleFilePermissions#padPermissions(FilePermissions, FilePermissions)
*/
public final void importPermissions(AbstractFile sourceFile, FilePermissions defaultPermissions) throws IOException {
changePermissions(SimpleFilePermissions.padPermissions(sourceFile.getPermissions(), defaultPermissions).getIntValue());
}
////////////////////
// Static methods //
////////////////////
/**
* Returns true if the specified file operation and corresponding method is supported by the
* given AbstractFile implementation.
* See the {@link FileOperation} enum for a complete list of file operations and their corresponding
* AbstractFile methods.
*
* @param op a file operation
* @param c the file implementation to test
* @return true if the specified file operation is supported by this filesystem.
* @see FileOperation
*/
public static boolean isFileOperationSupported(FileOperation op, Class extends AbstractFile> c) {
Method method = op.getCorrespondingMethod(c);
return method != null && !method.isAnnotationPresent(UnsupportedFileOperation.class);
}
/**
* Returns the given filename's extension, null if the filename doesn't have an extension.
*
*
* - it contains at least one . character
* - the last . is not the last character of the filename
* - the last . is not the first character of the filename
*
* .). For instance,
* this method will return "ext" for a file named "name.ext", not ".ext".
*
* @param filename a filename, not a full path
* @return the given filename's extension, null if the filename doesn't have an extension
*/
public static String getExtension(String filename) {
int lastDotPos = filename.lastIndexOf('.');
if (lastDotPos <= 0) {
return null;
}
int len = filename.length();
if (lastDotPos == len-1) {
return null;
}
return filename.substring(lastDotPos+1, len);
}
/**
* Returns the given filename without its extension (base name). if the filename doesn't have an extension, returns the filename as received
*
*
* - it contains at least one . character
* - the last . is not the last character of the filename
* - the last . is not the first character of the filename
*
* @return the file's base name - without its extension, if the filename doesn't have an extension returns the filename as received
*/
public String getBaseName() {
String fileName = getName();
int lastDotPos = fileName.lastIndexOf('.');
if (lastDotPos <= 0 || lastDotPos == fileName.length()-1) {
return fileName;
}
return fileName.substring(0, lastDotPos);
}
/**
* Returns the checksum (also referred to as hash or digest) of the given InputStream
* calculated by reading the stream and feeding the bytes to the given MessageDigest until EOF is
* reached.
*
* InputStream, and does not reset the
* MessageDigest after the checksum has been calculated.
*
* @param in the InputStream for which to calculate the checksum
* @param messageDigest the MessageDigest to use for calculating the checksum
* @return the given InputStream's checksum, as an hexadecimal string
* @throws IOException if an I/O error occurred while calculating the checksum
*/
public static String calculateChecksum(InputStream in, MessageDigest messageDigest) throws IOException {
ChecksumInputStream cin = new ChecksumInputStream(in, messageDigest);
try {
StreamUtils.readUntilEOF(cin);
return cin.getChecksumString();
} catch (IOException e) {
throw new FileTransferException(FileTransferException.READING_SOURCE);
}
}
////////////////////////
// Overridden methods //
////////////////////////
/**
* Tests a file for equality by comparing both files' {@link #getURL() URL}. Returns true if the URL
* of this file and the specified one are equal according to {@link FileURL#equals(Object, boolean, boolean)} called
* with credentials and properties comparison enabled.
*
* true if the URL of this file and the specified one are equal
* @see FileURL#equals(Object, boolean, boolean)
* @see #equalsCanonical(Object)
*/
public boolean equals(Object o) {
return o instanceof AbstractFile && getURL().equals(((AbstractFile) o).getURL(), true, true);
}
/**
* Tests a file for equality by comparing both files' {@link #getCanonicalPath() canonical path}.
* Returns true if the canonical path of this file and the specified one are equal.
*
* java.lang.String#equals(Object) to compare paths, which
* in some rare cases may return false for non-ascii/Unicode paths that have the same written
* representation but are not equal according to java.lang.String#equals(Object). Handling such cases
* would require a locale-aware String comparison which is not an option here.
*
* true if the canonical path of this file and the specified one are equal.
* @see #equals(Object)
*/
public boolean equalsCanonical(Object o) {
if (o instanceof AbstractFile) {
// TODO: resolve hostnames ?
return getCanonicalPath(false).equals(((AbstractFile)o).getCanonicalPath(false));
}
return false;
}
/**
* Returns the hashCode of this file's {@link #getURL() URL}.
*
* @return the hashCode of this file's {@link #getURL() URL}.
*/
public int hashCode() {
return getURL().hashCode();
}
/**
* Returns a String representation of this file. The returned String is this file's path as returned by
* {@link #getAbsolutePath()}.
*/
public String toString() {
return getAbsolutePath();
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Returns this file's last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
*
* @return this file's last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
*/
public abstract long getLastModifiedDate();
/**
* Returns this file's creation date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
*
* @throws IOException if an I/O error occurred
* @return creation date
*/
public long getCreationDate() throws IOException {
throw new IOException("operation not supported");
}
/**
* Returns this file's last access date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
*
* @throws IOException if an I/O error occurred
* @return last access date
*/
public long getLastAccessDate() throws IOException {
throw new IOException("operation not supported");
}
/**
* Changes this file's last modified date to the specified one. Throws an IOException if the date
* couldn't be changed, either because of insufficient permissions or because of an I/O error.
*
* 0 if this file doesn't exist, -1 if the size is
* undetermined.
*
* @return this file's size in bytes, 0 if this file doesn't exist, -1 if the size is undetermined
*/
public abstract long getSize();
/**
* Returns this file's parent, null if it doesn't have one.
*
* @return this file's parent, null if it doesn't have one
*/
public abstract AbstractFile getParent();
/**
* Sets this file's parent. null can be specified if this file doesn't have a parent.
*
* @param parent the new parent of this file
*/
public abstract void setParent(AbstractFile parent);
/**
* Returns true if this file exists.
*
* @return true if this file exists
*/
public abstract boolean exists();
/**
* Returns this file's permissions, as a {@link FilePermissions} object. Note that this file may only support
* certain permission bits, use the {@link com.mucommander.commons.file.FilePermissions#getMask() permission mask} to find
* out which bits are supported.
*
* null.
*
* @return this file's permissions, as a FilePermissions object
*/
public abstract FilePermissions getPermissions();
/**
* Returns a bit mask describing the permission bits that can be changed on this file when calling
* {@link #changePermission(int, int, boolean)} and {@link #changePermissions(int)}.
*
* @return a bit mask describing the permission bits that can be changed on this file
*/
public abstract PermissionBits getChangeablePermissions();
/**
* Changes the specified permission bit.
*
* AbstractFile implementation (cannot be retrieved or
* the filesystem doesn't have any notion of owner) or not available for this particular file, null
* will be returned.
*
* @return information about the owner of this file
*/
public abstract String getOwner();
/**
* Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.
* It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').
* If the owner information is not available to the AbstractFile implementation (cannot be retrieved or
* the filesystem doesn't have any notion of owner) or not available for this particular file, null
* will be returned.
*
* @return information about the owner of this file
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract short getReplication() throws UnsupportedFileOperationException;
/**
* Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.
* It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').
* If the owner information is not available to the AbstractFile implementation (cannot be retrieved or
* the filesystem doesn't have any notion of owner) or not available for this particular file, null
* will be returned.
*
* @return information about the owner of this file
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract long getBlocksize() throws UnsupportedFileOperationException;
/**
* Returns true if this file implementation is able to return some information about file owners, not
* necessarily for all files or this file in particular but at least for some of them. In other words, a
* true return value doesn't mean that {@link #getOwner()} will necessarily return a non-null value,
* but rather that there is a chance that it does.
*
* @return true if this file implementation is able to return information about file owners
*/
public abstract boolean canGetOwner();
/**
* Returns information about the group this file belongs to. The kind of information that is returned is implementation-dependant.
* It may typically be a group name (e.g. 'www-data') or a group ID (e.g. '501').
* If the group information is not available to the AbstractFile implementation (cannot be retrieved or
* the filesystem doesn't have any notion of owner) or not available for this particular file, null
* will be returned.
*
* @return information about the owner of this file
*/
public abstract String getGroup();
/**
* Returns true if this file implementation is able to return some information about file groups, not
* necessarily for all files or this file in particular but at least for some of them. In other words, a
* true return value doesn't mean that {@link #getGroup()} will necessarily return a non-null value,
* but rather that there is a chance that it does.
*
* @return true if this file implementation is able to return information about file groups
*/
public abstract boolean canGetGroup();
/**
* Returns true if this file is a directory, false in any of the following cases:
*
*
*
* @return true if this file is a directory, false in any of the cases listed above
*/
public abstract boolean isDirectory();
/**
* Returns true if this file is an archive.
* true if this file is an archive.
*/
public abstract boolean isArchive();
/**
* Returns true if this file is a symbolic link. Symbolic links need to be handled with special care,
* especially when manipulating files recursively.
*
* @return true if this file is a symbolic link
*/
public abstract boolean isSymlink();
/**
* Returns true if this file is a system file.
* Note that system file attribute depends on the OS, so we can know it only for local files:
* - For MAC OS, {@link MacOsSystemFolder} defines the group of system files
* - On Windows, files has special attribute that mark them as system files
*
* @return true if this file is a system file
*/
public abstract boolean isSystem();
/**
* Returns the children files that this file contains. For this operation to be successful, this file must be
* 'browsable', i.e. {@link #isBrowsable()} must return true.
* This method may return a zero-length array if it has no children but may never return null.
*
* IOException) if this file
* already exists.
*
* InputStream to read the contents of this file.
* Throws an IOException in any of the following cases:
*
*
* This method may never return null.
*
* InputStream to read the contents of this file
* @throws IOException in any of the cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract InputStream getInputStream() throws IOException;
/**
* Returns an OutputStream to write the contents of this file, overwriting the existing contents, if any.
* This file will be created as a zero-byte file if it does not yet exist.
* IOException in any of the following cases, but may never return
* null:
*
*
*
* OutputStream to write the contents of this file
* @throws IOException in any of the cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract OutputStream getOutputStream() throws IOException;
/**
* Returns an OutputStream to write the contents of this file, appending the existing contents, if any.
* This file will be created as a zero-byte file if it does not yet exist.
* IOException in any of the following cases, but may never return
* null:
*
*
*
* OutputStream to write the contents of this file
* @throws IOException in any of the cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract OutputStream getAppendOutputStream() throws IOException;
/**
* Returns a {@link RandomAccessInputStream} to read the contents of this file with random access.
* Throws an IOException in any of the following cases:
*
*
* This method may never return null.
*
* RandomAccessInputStream to read the contents of this file with random access
* @throws IOException in any of the cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract RandomAccessInputStream getRandomAccessInputStream() throws IOException;
/**
* Returns a {@link RandomAccessOutputStream} to write the contents of this file with random access.
* This file will be created as a zero-byte file if it does not yet exist.
* Throws an IOException in any of the following cases:
*
*
* This method may never return null.
*
* RandomAccessOutputStream to write the contents of this file with random access
* @throws IOException in any of the cases listed above
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract RandomAccessOutputStream getRandomAccessOutputStream() throws IOException;
/**
* Deletes this file and this file only (does not recurse on folders).
* Throws an IOException in any of the following cases:
*
*
*
* false.
*
*
*
*
*
*
*
* -1 if this information is
* not available.
*
* -1 if this information is
* not available.
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,
* or is not implemented.
*/
public abstract long getFreeSpace() throws IOException;
/**
* Returns the total space (in bytes) of the disk/volume where this file is.
*
* AbstractFile. Note however that the
* returned Object type may change over time, if the underlying API used to provide access to the filesystem
* changes, so this method should be used only as a last resort.
*
* null is returned.
*
* @return the file Object of the underlying API providing access to the filesystem, null if there
* is none
*/
public abstract Object getUnderlyingFileObject();
/**
* Returns the stream which can be re-used for file reading.
* All method calls will return the same class until the stream was closed.
* If the stream has been created but has insufficient buffer size it will be recreated.
* This method used for the file viewer and editor etc. to prevent multiple re-opening of files (and archives).
*
* @param bufferSize minimum size of buffer for PushbackInputStream.
* @return he stream which can be re-used for file reading
* @throws IOException if an I/O error occurred
*/
public synchronized PushbackInputStream getPushBackInputStream(final int bufferSize) throws IOException {
if (pushbackInputStream == null) {
pushbackInputStream = new MuPushbackInputStream(getInputStream(), bufferSize);
} else if (pushbackInputStream.getBufferSize() < bufferSize) {
pushbackInputStream.close();
pushbackInputStream = new MuPushbackInputStream(getInputStream(), bufferSize);
}
return pushbackInputStream;
}
/**
* Closes PushbackStream if it exists
* @throws IOException if an I/O error occurred
*/
public void closePushbackInputStream() throws IOException {
if (pushbackInputStream != null) {
pushbackInputStream.close();
}
}
public boolean isLocalFile() {
return FileProtocols.FILE.equals(fileURL.getScheme()) && hasAncestor(LocalFile.class);
}
/**
*
*/
private class MuPushbackInputStream extends PushbackInputStream implements HasProgress {
private final InputStream src;
// public MuPushbackInputStream(InputStream in) {
// super(in);
// src = in;
// }
MuPushbackInputStream(InputStream in, int size) {
super(in, size);
src = in;
}
@Override
public void close() throws IOException {
synchronized (AbstractFile.this) {
super.close();
src.close();
pushbackInputStream = null;
}
}
int getBufferSize() {
return buf.length;
}
@Override
public int getProgress() {
return hasProgress() ? ((HasProgress)in).getProgress() : -1;
}
@Override
public boolean hasProgress() {
return in instanceof HasProgress;
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractFileClassLoader.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ClassLoader implementation capable of loading classes from instances of {@link AbstractFile}.
* AbstractFileClassLoader.
* @param parent parent of the class loader.
*/
public AbstractFileClassLoader(ClassLoader parent) {
super(parent);
files = new Vector<>();
}
/**
* Creates a new AbstractFileClassLoader that uses the system classloader as a parent.
*/
public AbstractFileClassLoader() {
this(ClassLoader.getSystemClassLoader());
}
/**
* Adds the specified file to the class loader's classpath.
* file is not browsable.
*/
public void addFile(AbstractFile file) {
// Makes sure the specified file is browsable.
if (!file.isBrowsable()) {
throw new IllegalArgumentException();
}
// Only adds the file if it's not already there.
if (!contains(file)) {
files.add(file);
}
}
/**
* Returns an iterator on all files in this loader's classpath.
* @return an iterator on all files in this loader's classpath.
*/
public Iteratortrue if this loader's classpath already contains the specified file.
* @param file file to look for.
* @return true if this loader's classpath already contains the specified file.
*/
public boolean contains(AbstractFile file) {
return files.contains(file);
}
// - Resource access -------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Tries to locate the specified resource and returns an AbstractFile instance on it.
* @param name name of the resource to locate.
* @return an {@link AbstractFile} instance describing the requested resource if found, null otherwise.
*/
private AbstractFile findResourceAsFile(String name) {
for (AbstractFile file : files) {
try {
// If the requested resource could be found, returns it.
final AbstractFile child = file.getChild(name);
if (child.exists()) {
return child;
}
} catch(IOException ignore) {
if (LOGGER != null) {
LOGGER.info("");
}
}
// Treats error as a simple 'resource not found' case and keeps looking for
// one with the correct name that will load.
}
// The requested resource wasn't found.
return null;
}
/**
* Returns an input stream on the requested resource.
* @param name name of the resource to open.
* @return an input stream on the requested resource, null if not found.
*/
@Override
public InputStream getResourceAsStream(String name) {
InputStream in = getParent().getResourceAsStream(name); // Input stream on the resource.
// Tries the parent first, to respect the delegation model.
if (in != null) {
return in;
}
// Tries to locate the resource in the extended classpath if it wasn't found in the parent.
AbstractFile file = findResourceAsFile(name); // File representing the resource.
if (file != null) {
try {
return file.getInputStream();
} catch(Exception e) {
if (LOGGER != null) {
LOGGER.info("", e);
}
}
}
// Couldn't find the resource.
return null;
}
/**
* Tries to find the requested resource.
* @param name name of the resource to locate.
* @return the URL of the requested resource if found, null otherwise.
*/
@Override
protected URL findResource(String name) {
AbstractFile file = findResourceAsFile(name); // Path to the requested resource.
// Tries to find the resource.
if (file == null) {
return null;
}
// Tries to retrieve an URL on the resource.
try {
return file.getJavaNetURL();
} catch(Exception e) {
return null;
}
}
/**
* Tries to find all the resources with the specified name.
* @param name of the resources to find.
* @return an enumeration containing the URLs of all the resources that match name.
*/
@Override
protected Enumerationnull otherwise.
*/
@Override
protected String findLibrary(String name) {
AbstractFile file = findResourceAsFile(name); // Path of the requested library.
return file == null ? null : file.getAbsolutePath();
}
// - Class loading ---------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Loads and returns the class defined by the specified name and path.
* @param name name of the class to load.
* @param file file containing the class' bytecode.
* @return the class defined by the specified name and path.
* @throws IOException if an error occurs.
*/
private Class> loadClass(String name, AbstractFile file) throws IOException {
byte[] buffer = new byte[(int)file.getSize()]; // Buffer for the class' bytecode.
int offset = 0; // Current offset in buffer.
try (InputStream in = file.getInputStream()) {
// Loads the content of file in buffer.
while (offset != buffer.length) {
offset += in.read(buffer, offset, buffer.length - offset);
}
// Loads the class.
return defineClass(name, buffer, 0, buffer.length);
}
}
/**
* Tries to find and load the specified class.
* @param name fully qualified name of the class to load.
* @return the requested Class if found, null otherwise.
* @throws ClassNotFoundException if the requested class was not found.
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
AbstractFile file = findResourceAsFile(name.replace('.', '/') + ".class"); // File containing the class' bytecode.
// Tries to locate the specified class and, if found, load it.
if (file != null) {
try {
return loadClass(name, file);
} catch(IOException e) {
if (LOGGER != null) {
LOGGER.info("", e);
}
}
}
throw new ClassNotFoundException(name);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractROArchiveFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractROArchiveFile represents a read-only archive file. This class is abstract and implemented
* by read-only archive files.
*
* AbstractROArchiveFile implementations only have to provide two methods:
*
*
* The {@link #isWritable()} method is implemented to always returns false.
*
* @author Maxence Bernard
*/
public abstract class AbstractROArchiveFile extends AbstractArchiveFile {
/**
* Creates an AbstractROArchiveFile on top of the given file.
*
* @param file the file on top of which to create the archive
*/
protected AbstractROArchiveFile(AbstractFile file) {
super(file);
}
/**
* Returns false: AbstractROArchiveFile implementations are not capable of adding or
* deleting entries.
*
* @return false
*/
@Override
public final boolean isWritable() {
return false;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AbstractRWArchiveFile.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractRWArchiveFile represents a read-write archive file. This class is abstract and implemented by
* all read-write archive files.
* In addition to the read-only operations defined by {@link com.mucommander.commons.file.AbstractArchiveFile}, it provides
* abstract methods for adding and deleting entries from the archive.
*
* The {@link #isWritable()} method implemented by this class always returns true. However,
* write operations may not always be available depending on the underlying file (e.g. if random file access is
* required). In that case, {@link #isWritable ()} should be overridden to return true only when
* write operations are available.
*
* @author Maxence Bernard
*/
public abstract class AbstractRWArchiveFile extends AbstractArchiveFile {
/**
* Creates an AbstractRWArchiveFile on top of the given file.
*
* @param file the file on top of which to create the archive
*/
protected AbstractRWArchiveFile(AbstractFile file) {
super(file);
}
/**
* Returns true: AbstractRWArchiveFile implementations are by definition capable of adding
* or deleting entries. This method should be overridden if the implementation is capable of providing write access
* only under certain conditions, for example if it requires random access to the proxied archive file which may not
* always be available depending on the underlying file. If that is the case, this method should return
* true only when all conditions for providing write operations are met.
*
* @return true, always
*/
@Override
public boolean isWritable() {
return true;
}
/**
* Adds the given entry to the archive and returns an OutputStream to write the entry's contents
* if the entry is a regular file, null if the entry is a directory.
* Throws an IOException if the entry already exists in the archive or if an I/O error occurs.
*
* @param entry the entry to add to the archive
* @return an OutputStream to write the entry's contents if the entry is a regular file, null if the entry is a directory
* @throws IOException if the entry already exists in the archive or if an I/O error occurs
* @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by
* the underlying file protocol.
*/
public abstract OutputStream addEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;
/**
* Deletes the specified entry from the archive. Throws an IOException if the entry doesn't exist
* in the archive or if an I/O error occurs.
*
* @param entry the entry to delete from the archive
* @throws IOException if the entry doesn't exist in the archive or if an I/O error occurs
* @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by
* the underlying file protocol.
*/
public abstract void deleteEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;
/**
* Updates the specified entry in the archive with the attributes contained in the {@link ArchiveEntry} object.
* Throws an IOException if the entry doesn't exist in the archive or if an I/O error occurs.
*
* true if the entry exists in the archive
*/
public ArchiveEntry(String path, boolean directory, long date, long size, boolean exists) {
setPath(path);
setDate(date);
setSize(size);
setDirectory(directory);
setExists(exists);
}
/**
* Returns the depth of this entry based on the number of path delimiters ({@link #SEPARATOR_CHAR}) its path
* contains. Top-level entries have a depth of 1.
*
* @return the depth of this entry
*/
public int getDepth() {
return getDepth(getPath());
}
/**
* Returns the depth of the specified entry path, based on the number of path delimiters ({@link #SEPARATOR_CHAR})
* it contains. Top-level entries have a depth of 1.
*
* @param entryPath the path for which to calculate the depth
* @return the depth of the given entry path
*/
static int getDepth(String entryPath) {
return PathUtils.getDepth(entryPath, SEPARATOR_STRING);
}
/**
* Extracts this entry's filename from its path and returns it.
*
* @return this entry's filename
*/
public String getName() {
String path = getPath();
int len = path.length();
// Remove trailing '/' if any
if (path.charAt(len-1) == SEPARATOR_CHAR) {
path = path.substring(0, --len);
}
int lastSlash = path.lastIndexOf(SEPARATOR_CHAR);
return lastSlash == -1 ? path : path.substring(lastSlash+1, len);
}
/**
* Returns an archive format-dependent object providing extra information about this entry, typically an object from
* a 3rd party library ; null if this entry has none.
*
* @return an object providing extra information about this entry, null if this entry has none
*/
public Object getEntryObject() {
return entryObject;
}
/**
* Sets an archive format-dependent object providing extra information about this entry, typically an object from
* a 3rd party library ; null for none.
*
* @param entryObject an object providing extra information about this entry, null for none
*/
public void setEntryObject(Object entryObject) {
this.entryObject = entryObject;
}
/**
* Returns the file permissions of this entry. This method is overridden to return default permissions
* ({@link FilePermissions#DEFAULT_DIRECTORY_PERMISSIONS} for directories, {@link FilePermissions#DEFAULT_FILE_PERMISSIONS}
* for regular files), when none have been set.
*
* @return the file permissions of this entry
*/
@Override
public FilePermissions getPermissions() {
FilePermissions permissions = super.getPermissions();
if (permissions == null) {
return isDirectory() ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS : FilePermissions.DEFAULT_FILE_PERMISSIONS;
}
return permissions;
}
/**
* Overridden to invalidates any previously computed hash code.
*
* @param path new path to set
*/
@Override
public void setPath(String path) {
super.setPath(path);
// Invalidate any previously
hashCode = 0;
}
/**
* Returns true if the given object is an ArchiveEntry whose path is equal to this one,
* according to {@link PathUtils#pathEquals(String, String, String)} (trailing slash-insensitive comparison).
*
* @param o the object to test
* @return true if the given object is an ArchiveEntry whose path is equal to this one
* @see PathUtils#pathEquals(String, String, String)
*/
public boolean equals(Object o) {
return o instanceof ArchiveEntry && PathUtils.pathEquals(getPath(), ((ArchiveEntry) o).getPath(), SEPARATOR_STRING);
}
/**
* This method is overridden to return a hash code that is consistent with {@link #equals(Object)},
* so that url1.equals(url2) implies url1.hashCode()==url2.hashCode().
*/
public int hashCode() {
if (hashCode != 0) { // Return any previously computed hashCode. Note that setPath invalidates the hashCode.
return hashCode;
}
String path = getPath();
// #equals(Object) is trailing separator insensitive, so the hashCode must be trailing separator invariant
hashCode = path.endsWith(SEPARATOR_STRING) ? path.substring(0, path.length()-1).hashCode() : path.hashCode();
return hashCode;
}
@Override
public String toString() {
return " ArchiveEntry(" + entryObject + ")";
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ArchiveEntryIterator.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Iterator, with
* several differences:
*
*
*
*
* @see com.mucommander.commons.file.SingleArchiveEntryIterator
* @see com.mucommander.commons.file.WrapperArchiveEntryIterator
* @author Maxence Bernard
*/
public interface ArchiveEntryIterator {
/**
* Returns the next entry in this iterator, IOExceptionhasNext method, because it wouldn't map very well onto certain formats that don't know
* if there is a next entry until the current entry has been consumed.null if this iterator has no more entries.
*
* @return true if this iterator has a next entry
* @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or
* because of an I/O error
*/
ArchiveEntry nextEntry() throws IOException;
/**
* Closes this iterator and releases all the resources it holds. This method must be called when this iterator
* is not needed anymore.
*
* @throws IOException if an error occurred while closing the resources
*/
void close() throws IOException;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ArchiveEntryTree.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ArchiveEntryTree also acts as the root node: all entry nodes
* are children of it (direct or indirect).
*
* @author Maxence Bernard
*/
class ArchiveEntryTree extends DefaultMutableTreeNode {
private static Logger logger;
/**
* Creates a new empty tree.
*/
ArchiveEntryTree() {
}
/**
* Adds the given entry to the archive tree, creating parent nodes as necessary.
*
* @param entry the entry to add to the tree
*/
void addArchiveEntry(ArchiveEntry entry) {
String entryPath = entry.getPath();
int entryDepth = entry.getDepth();
int slashPos = 0;
DefaultMutableTreeNode node = this;
for (int d = 1; d <= entryDepth; d++) {
if (d == entryDepth && !entry.isDirectory()) {
// create a leaf node for the entry
entry.setExists(true); // the entry has to exist
node.add(new DefaultMutableTreeNode(entry, true));
break;
}
String subPath = d==entryDepth?entryPath:entryPath.substring(0, (slashPos=entryPath.indexOf('/', slashPos)+1));
int nbChildren = node.getChildCount();
DefaultMutableTreeNode childNode = null;
boolean matchFound = false;
for (int c = 0; c < nbChildren; c++) {
childNode = (DefaultMutableTreeNode)node.getChildAt(c);
// Path comparison is 'trailing slash insensitive'
if (PathUtils.pathEquals(((ArchiveEntry)childNode.getUserObject()).getPath(), subPath, "/")) {
// Found a match
matchFound = true;
break;
}
}
if (matchFound) {
if (d == entryDepth) {
getLogger().trace("Replacing entry for node {}", childNode);
childNode.setUserObject(entry); // Replace existing entry
} else {
node = childNode;
}
} else {
if (d == entryDepth) {
// create a leaf node for the entry
entry.setExists(true); // the entry has to exist
node.add(new DefaultMutableTreeNode(entry, true));
} else {
getLogger().trace("Creating node for {}", subPath);
childNode = new DefaultMutableTreeNode(new ArchiveEntry(subPath, true, entry.getLastModifiedDate(), 0, true), true);
node.add(childNode);
node = childNode;
}
}
}
}
/**
* Finds and returns the node that corresponds to the specified entry path, null if no entry matching
* the path could be found.
*
* AbstractArchiveFile .
*
* @param file file to map as an AbstractArchiveFile.
* @return a new instance of AbstractArchiveFile that matches the specified URL.
* @throws IOException if an error occurs.
*/
AbstractArchiveFile getFile(AbstractFile file) throws IOException;
/**
* Returns the FilenameFilter that matches filenames to be associated with this archive format.
*
* @return the FilenameFilter that matches filenames to be associated with this archive format
*/
FilenameFilter getFilenameFilter();
String[] getFileExtensions();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AuthException.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see IOException that is thrown whenever an operation failed due to the lack of,
* invalid or insufficient credentials. A URL associated with the exception gives the location where the error
* occurred, and the set of credentials that were used (if any).
*
* @author Maxence Bernard
*/
public class AuthException extends IOException {
protected FileURL fileURL;
protected String msg;
/**
* Creates a new AuthException instance, without any associated exception.
*
* @param fileURL the location where the error occurred, with the set of credentials that were used (if any).
*/
public AuthException(FileURL fileURL) {
this(fileURL, null);
}
/**
* Creates a new AuthException instance that was caused by the given exception.
*
* @param fileURL the location where the error occurred, with the set of credentials that were used (if any)
* @param msg a message describing the error, null if there is none
*/
public AuthException(FileURL fileURL, String msg) {
super(msg);
this.fileURL = fileURL;
if (msg != null) {
this.msg = msg.trim();
}
}
/**
* Returns the location where the error occurred, with the set of credentials that were used (if any).
*
* @return the location where the error occurred, with the set of credentials that were used (if any)
*/
public FileURL getURL() {
return fileURL;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/AuthenticationType.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see null.
*
* @return the login part.
*/
public String getLogin() {
return login;
}
/**
* Returns the password part. The returned password may be an empty string but never null.
*
* @return the password part.
*/
public String getPassword() {
return password;
}
/**
* Returns the password as a masked string, each of the characters replaced by '*' characters.
*
* @return the password as a masked string.
*/
public String getMaskedPassword() {
int passwordLength = password.length();
return "*".repeat(passwordLength);
}
/**
* Returns true if these credentials are empty.
* true if these credentials are empty, false otherwise.
*/
public boolean isEmpty() {
return "".equals(login) && "".equals(password);
}
/**
* This method is equivalent to calling {@link #equals(Object, boolean)} with false:
* two Credentials instances with the same login but a different password are considered equal.
*
* @param o the Object to test for equality
* @return true if this and the specified instance are equal
* @see #equals(Object, boolean)
*/
@Override
public boolean equals(Object o) {
return equals(o, false);
}
/**
* Returns true if these Credentials and the specified instance are equal. For credentials to be equal,
* their login (as returned by {@link #getLogin()} must be equal. If the password-sensitive parameter is enabled,
* their passwords (as returned by {@link #getPassword()} must also match.
*
* null are considered equal: if a null instance is specified,
* true is returned if these Credentials are {@link #isEmpty() empty}).
*
* @param o the Object to test for equality
* @param passwordSensitive true if passwords need to be equal for credentials instances to match
* @return true if this and the specified instance are equal
*/
public boolean equals(Object o, boolean passwordSensitive) {
// Empty Credentials and null are equivalent
if (o == null) {
return isEmpty();
}
if (!(o instanceof Credentials)) { // Note: this class is declared final so we don't need to worry about subclasses
return false;
}
Credentials credentials = (Credentials)o;
return credentials.login.equals(this.login) && (!passwordSensitive || credentials.password.equals(this.password));
}
/**
* Returns a cloned instance of these Credentials.
*
* @return a cloned instance of these Credentials
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// Should never happen
return null;
}
}
public String toString() {
return login;
}
public int hashCode() {
// Do not take into account the password, as #equals(Object) is password-insensitive
return login.hashCode();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/DefaultPathCanonizer.java
================================================
package com.mucommander.commons.file;
import java.util.Vector;
/**
* @author Maxence Bernard
*/
public class DefaultPathCanonizer implements PathCanonizer {
/** Path separator */
private final String separator;
/** The string replacement for '~' path fragments, null for no tilde replacement */
private final String tildeReplacement;
/**
* Creates a new path canonizer using the specified path separator and no tilde replacement.
*
* @param separator the path separator that delimits path fragments
*/
public DefaultPathCanonizer(String separator) {
this(separator, null);
}
/**
* Creates a new path canonizer using the specified path separator and tilde replacement string
* (if not null).
*
* @param separator the path separator that delimits path fragments
* @param tildeReplacement if not null, path fragments equal to '~' will be replaced by this string.
*/
public DefaultPathCanonizer(String separator, String tildeReplacement) {
this.separator = separator;
this.tildeReplacement = tildeReplacement;
}
/**
* Returns a canonical value of the given path, where '.' and '..' path fragments are factored out,
* and '~' fragments replaced by the string specified in the constructor (if not null).
*
* @param path the path to canonize
* @return the canonized path
*/
@Override
public String canonize(String path) {
// Todo: use PathTokenizer?
if (!path.equals("/")) {
int pos; // position of current path separator
int pos2 = 0; // position of next path separator
int separatorLen = separator.length();
String dir; // Current directory
String dirWS; // Current directory without trailing separator
Vector
* The multi-arg constructor allows to create a scheme handler with specific values.
* "/". This behavior can be modified by overriding getRealm.
*
* @see com.mucommander.commons.file.FileURL#getDefaultHandler()
* @see com.mucommander.commons.file.SchemeHandler
* @author Maxence Bernard
*/
public class DefaultSchemeHandler implements SchemeHandler {
protected SchemeParser parser;
protected int standardPort;
protected String pathSeparator;
protected AuthenticationType authenticationType;
protected Credentials guestCredentials;
/**
* Creates a DefaultSchemeHandler with default values that suit schemes in which the scheme name is not included
* in the URL (local and unc locations):
*
*
*/
public DefaultSchemeHandler() {
this(new DefaultSchemeParser(), -1, System.getProperty("file.separator"), AuthenticationType.NO_AUTHENTICATION, null);
}
/**
* Creates a DefaultSchemeHandler with the specified values.
*
* @param parser the parser that takes care of parsing URL strings and turning them into FileURL
* @param standardPort the scheme's standard port, -1null-1 for none
* @param pathSeparator the scheme's path separator, cannot be null
* @param authenticationType the type of authentication used by the scheme's file protocol
* @param guestCredentials the scheme's guest credentials, null for none
*/
public DefaultSchemeHandler(SchemeParser parser, int standardPort, String pathSeparator, AuthenticationType authenticationType, Credentials guestCredentials) {
this.parser = parser;
this.standardPort = standardPort;
this.pathSeparator = pathSeparator;
this.authenticationType = authenticationType;
this.guestCredentials = guestCredentials;
}
//////////////////////////////////
// SchemeHandler implementation //
//////////////////////////////////
/**
* Returns the parser that was passed to the constructor.
*
* @return the parser that was passed to the constructor
*/
public SchemeParser getParser() {
return parser;
}
/**
* Returns the authentication type that was passed to the constructor.
*
* @return the authentication type that was passed to the constructor
*/
public AuthenticationType getAuthenticationType() {
return authenticationType;
}
/**
* Returns the set of guest credentials that was passed to the constructor.
*
* @return the set of guest credentials that was passed to the constructor
*/
public Credentials getGuestCredentials() {
return guestCredentials;
}
/**
* Returns the path separator that was passed to the constructor.
*
* @return the path separator that was passed to the constructor
*/
public String getPathSeparator() {
return pathSeparator;
}
/**
* Returns the standard port that was passed to the constructor.
*
* @return the standard port that was passed to the constructor
*/
public int getStandardPort() {
return standardPort;
}
/**
* Returns a URL with the same scheme, host and port (if any) as the specified URL, and a path set to
* "/" or "\" depending on the URL format.
* The login, password, query and fragment parts of the returned URL are always null.
* For example, when called with {@code http://www.mucommander.com:8080/path/to/file?query¶m=value},
* this method returns http://www.mucommander.com:8080/.
*
* @param location the location for which to return the authentication realm
* @return the authentication realm of the specified location
*/
public FileURL getRealm(FileURL location) {
// Start by cloning the given URL and then modify the parts that need it
FileURL realm = (FileURL)location.clone();
realm.setPath(location.getPathSeparator());
realm.setCredentials(null);
realm.setQuery(null);
// Todo
// realm.setFragment(null)
return realm;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/DefaultSchemeParser.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Local paths
*
*
*
* C:\Windows\System32\ will be parsed and turned into a FileURL whose path
* separator is "\" and representation file://localhost/C:\Windows\System32\C:\Windows\System32\ will be parsed and turned
* into a FileURL whose path separator is "\" and representation file://localhost/C:\Windows\System32\UNC paths
* \\Server\Volume\File are supported on all OSes but the FileURL
* resulting from the parsing varies will not be the same whether they are created on a Windows environment or
* on another:
*
*
*
* @see PathCanonizer
* @author Maxence Bernard
*/
@Slf4j
public class DefaultSchemeParser implements SchemeParser {
/** True if query should be parsed and not considered as part of the path */
private final boolean parseQuery;
/** \\Server\Volume\File will be turned into a FileURL whose string
* representation is file://Server/\Volume\File\\Server\Volume\File will be turned into a FileURL whose string
* representation is smb://Server/Volume/FilePathCanonizer instance to be used for canonizing the path part */
private final PathCanonizer pathCanonizer;
/**
* Creates a DefaultSchemeParser with a {@link DefaultPathCanonizer} that uses the operating system's default
* path separator as the path separator and no tilde replacement, and query parsing disabled.
*/
DefaultSchemeParser() {
this(false);
}
/**
* Creates a DefaultSchemeParser with a {@link DefaultPathCanonizer} that uses the operating system's
* default path separator as the path separator and no tilde replacement.
* If parseQuery is true, any query part (delimited by '?') will be parsed as such,
* or considered as part of the path otherwise.
*
* @param parseQuery true, any query part (delimited by '?') will be parsed as such, or considered
* as part of the path otherwise
*/
DefaultSchemeParser(boolean parseQuery) {
this(new DefaultPathCanonizer(FileSystems.getDefault().getSeparator(), null), parseQuery);
}
/**
* Creates a DefaultSchemeParser using the specified {@link PathCanonizer} for canonizing the path part.
* If parseQuery is true, any query part (delimited by '?') will be parsed as such,
* or considered as part of the path otherwise.
*
* @param pathCanonizer PathCanonizer instance to be used for canonizing the path part
* @param parseQuery true, any query part (delimited by '?') will be parsed as such, or considered
* as part of the path otherwise
*/
DefaultSchemeParser(PathCanonizer pathCanonizer, boolean parseQuery) {
this.parseQuery = parseQuery;
this.pathCanonizer = pathCanonizer;
}
/**
* Handles the parsing of the given local file URL.
*
* @param url the URL to parse
* @param fileURL the FileURL instance in which to set the different parsed parts
*/
private void handleLocalFilePath(String url, FileURL fileURL) {
SchemeHandler handler = FileURL.getRegisteredHandler(FileProtocols.FILE);
SchemeParser parser = handler.getParser();
fileURL.setHandler(handler);
fileURL.setScheme(FileProtocols.FILE);
fileURL.setHost(FileURL.LOCALHOST);
fileURL.setPath((parser instanceof DefaultSchemeParser?((DefaultSchemeParser)parser).getPathCanonizer():pathCanonizer).canonize(url));
}
/**
* Returns the {@link PathCanonizer} instance that is used by this {@link DefaultSchemeParser}.
*
* @return the {@link PathCanonizer} instance that is used by this {@link DefaultSchemeParser}
*/
private PathCanonizer getPathCanonizer() {
return pathCanonizer;
}
@Override
public void parse(String url, FileURL fileURL) throws MalformedURLException {
// The general form of a URI is:
// foo://example.com:8042/over/there?name=ferret#nose
// \_/ \______________/\_________/ \_________/ \__/
// | | | | |
// scheme authority path query fragment
// | _____________________|__
// / \ / \
// urn:example:animal:ferret:nose
// See http://labs.apache.org/webarch/uri/rfc/rfc3986.html for full specs
try {
int pos;
int schemeDelimPos = url.indexOf("://");
int urlLen = url.length();
// If the given url contains no scheme, consider that it is a local path and transform it into a file:// URL
if (schemeDelimPos == -1) {
// Treat the URL as local file path if it starts with:
// - '/' and OS doesn't use root drives (Unix-style path)
// - a drive letter and OS uses root drives (Windows-style) [support both C:\ and C:/ style]
// - a ~ character (refers to the user home folder)
if ((!LocalFile.USES_ROOT_DRIVES && url.startsWith("/")) || url.startsWith("~/") || url.equals("~")) {
handleLocalFilePath(url, fileURL);
// All done, return
return;
} else if (LocalFile.USES_ROOT_DRIVES && (url.indexOf(":\\") == 1 || url.indexOf(":/") == 1)) {
// Turn forward slash-separated paths into their backslash-separated counterparts.
if (url.charAt(2) == '/') {
url = url.replace('/', '\\');
}
handleLocalFilePath(url, fileURL);
// All done, return
return;
}
// Handle Windows-style UNC network paths ( \\hostname\path ):
// - under Windows, transform it into a URL in the file://hostname/path form,
// LocalProtocolProvider will translate it back into a UNC network path
// - under other OS, conveniently transform it into smb://hostname/path to be nice with folks
// who've spent too much time using Windows
else if (url.startsWith("\\\\") && urlLen > 2) {
if (OsFamily.WINDOWS.isCurrent()) {
pos = url.indexOf('\\', 2);
url = FileProtocols.FILE+"://"+
(pos==-1?url.substring(2):url.substring(2, pos)+"/"+(pos==urlLen-1?"":url.substring(pos+1)));
// Update scheme delimiter position
schemeDelimPos = FileProtocols.FILE.length();
}
else {
url = FileProtocols.SMB+"://"+url.substring(2).replace('\\', '/');
// Update scheme delimiter position
schemeDelimPos = FileProtocols.SMB.length();
}
// Update URL's length
urlLen = url.length();
} else { // This doesn't look like a valid path, throw an MalformedURLException
throw new MalformedURLException("Path not absolute or malformed: "+url);
}
}
// Start URL parsing
String scheme = url.substring(0, schemeDelimPos);
fileURL.setScheme(scheme);
// Advance string index
pos = schemeDelimPos+3;
int separatorPos = url.indexOf('/', pos);
// The question mark character (if any) marks the beginning of the query part, only if it should be parsed.
int questionMarkPos = parseQuery?url.indexOf('?', pos):-1;
int hostEndPos; // Contains the position of the beginning of the path/query part
if (separatorPos != -1) { // Separator is necessarily before question mark
hostEndPos = separatorPos;
} else if (questionMarkPos != -1) {
hostEndPos = questionMarkPos;
} else {
hostEndPos = urlLen;
}
// The authority part is the one between scheme:// and the path/query. It includes the user information
// (login/password), host and port.
String authority = url.substring(pos, hostEndPos);
pos = 0;
// Parse login and password (if specified).
// They may contain non-URL safe characters that are decoded here, and re-encoded by FileURL#toString.
int atPos = authority.lastIndexOf('@');
int colonPos;
// Filenames may contain @ chars, so atPos must be lower than next separator's position (if any)
if (atPos != -1 && (separatorPos == -1 || atPos < separatorPos)) {
colonPos = authority.indexOf(':');
String login = URLDecoder.decode(authority.substring(0, colonPos == -1 ? atPos : colonPos), StandardCharsets.UTF_8);
String password;
if (colonPos != -1) {
password = URLDecoder.decode(authority.substring(colonPos+1, atPos), StandardCharsets.UTF_8);
} else {
password = null;
}
if (!login.isEmpty() || !(password == null || password.isEmpty())) {
fileURL.setCredentials(new Credentials(login, password));
}
// Advance string index
pos = atPos+1;
}
// Parse host and port (if specified)
colonPos = authority.indexOf(':', pos);
String host;
if (colonPos != -1) {
host = authority.substring(pos, colonPos);
String portString = authority.substring(colonPos+1);
if (!portString.isEmpty()) { // Tolerate an empty port part (e.g. http://mucommander.com:/)
try {
fileURL.setPort(Integer.parseInt(portString));
} catch(NumberFormatException e) {
throw new MalformedURLException("URL contains an invalid port");
}
}
} else {
host = authority.substring(pos);
}
if (host.isEmpty()) {
host = null;
}
fileURL.setHost(host);
// Parse path part excluding query part
pos = hostEndPos;
String path = url.substring(pos, questionMarkPos==-1?urlLen:questionMarkPos);
// Empty path means '/'
if (path.isEmpty()) {
path = "/";
}
// Canonize path: factor out '.' and '..' and replace '~' by the replacement string (if any)
fileURL.setPath(pathCanonizer.canonize(path));
// log.debug("Warning: path should not be empty, url={}", url);
// Parse query part (if any)
if (questionMarkPos != -1) {
fileURL.setQuery(url.substring(questionMarkPos+1)); // Do not include the question mark
}
} catch (MalformedURLException e) {
throw e;
} catch (Exception e2) {
log.info("Unexpected exception in FileURL() with {}", url, e2);
throw new MalformedURLException();
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/DummyFile.java
================================================
package com.mucommander.commons.file;
import com.mucommander.commons.io.RandomAccessInputStream;
import com.mucommander.commons.io.RandomAccessOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* This class is an implementation of AbstractFile which implements all methods as no-op (that do nothing)
* that return default values. It makes it easy to quickly create a AbstractFile implementation by simply
* overriding the methods that are needed, for example as an anonymous class inside a method.
*
* 0.
*/
@Override
public long getLastModifiedDate() {
return 0;
}
/**
* Implementation notes: always throws {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always.
*/
@Override
@UnsupportedFileOperation
public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);
}
/**
* Implementation notes: always returns -1.
*/
@Override
public long getSize() {
return -1;
}
/**
* Implementation notes: always returns null.
*/
@Override
public AbstractFile getParent() {
return null;
}
/**
* Implementation notes: no-op, does nothing with the specified parent.
*/
@Override
public void setParent(AbstractFile parent) {
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean exists() {
return false;
}
/**
* Implementation notes: always returns {@link FilePermissions#EMPTY_FILE_PERMISSIONS}.
*/
@Override
public FilePermissions getPermissions() {
return FilePermissions.EMPTY_FILE_PERMISSIONS;
}
/**
* Implementation notes: returns {@link PermissionBits#EMPTY_PERMISSION_BITS}, none of the permission bits can be
* changed.
*/
@Override
public PermissionBits getChangeablePermissions() {
return PermissionBits.EMPTY_PERMISSION_BITS;
}
/**
* Implementation notes: always returns false.
*/
@Override
@UnsupportedFileOperation
public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);
}
/**
* Implementation notes: always returns null.
*/
@Override
public String getOwner() {
return null;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean canGetOwner() {
return false;
}
/**
* Implementation notes: always returns null.
*/
@Override
public String getGroup() {
return null;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean canGetGroup() {
return false;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean isDirectory() {
return false;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean isArchive() {
return false;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean isSymlink() {
return false;
}
/**
* Implementation notes: always returns false.
*/
@Override
public boolean isSystem() {
return false;
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public AbstractFile[] ls() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.LIST_CHILDREN);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void mkdir() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public InputStream getInputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.READ_FILE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public OutputStream getOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void delete() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.DELETE);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);
}
/**
* Implementation notes: always throws an {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RENAME);
}
/**
* Implementation notes: always throws {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public long getFreeSpace() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);
}
/**
* Implementation notes: always throws {@link UnsupportedFileOperationException}.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public long getTotalSpace() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);
}
/**
* Implementation notes: always returns null.
*/
@Override
public Object getUnderlyingFileObject() {
return null;
}
@Override
@UnsupportedFileOperation
public short getReplication() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);
}
@Override
@UnsupportedFileOperation
public long getBlocksize() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);
}
@Override
@UnsupportedFileOperation
public void changeReplication(short replication) throws IOException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FileAccessDeniedException.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2016 Oleg Trifonov
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
*
* null by default. The type of path (relative or absolute) separator character
* are unspecified and context-dependant.false by default0 (00:00:00 GMT, January 1, 1970) by default0 by defaultfalse by defaultnull if
* undefinednull by defaultnull by defaultnull by default.
*
* null by default
*/
String getPath();
/**
* Returns true if the file exists physically on the underlying filesystem, false
* by default.
*
* @return true if the file exists physically on the underlying filesystem, false by default
*/
boolean exists();
/**
* Returns the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), 0 by default
*
* @return the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), 0 by default
*/
long getLastModifiedDate();
/**
* Returns the file's size in bytes.
*
* @return the file's size in bytes
*/
long getSize();
/**
* Returns true if the file is a directory, false if it is a regular file
* (defaults to false).
*
* @return true if the file is a directory, false if it is a regular file or undefined
*/
boolean isDirectory();
/**
* Returns the file's permissions, null by default.
*
* @return the file's permissions, null by default
*/
FilePermissions getPermissions();
/**
* Returns the file's owner, null by default.
*
* @return the file's owner, null by default
*/
String getOwner();
/**
* Returns the file's group, null by default.
*
* @return the file's group, null by default
*/
String getGroup();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FileFactory.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Protocols
* com.mucommander.commons.file API to access new file protocols, developers must create
* an implementation of {@link AbstractFile} that handles that protocol and register it to FileFactory.
* This registration requires an implementation of {@link ProtocolProvider}, an instance of which will be passed to
* {@link #registerProtocol(String,ProtocolProvider) registerProtocol}.
*
*
*
*
* Archive formats
* com.mucommander.commons.file API to access new archive formats, developers must create
* an implementation of {@link AbstractArchiveFile} that handles that format and register it to FileFactory.
* This registration requires an implementation of {@link ArchiveFormatProvider}, an instance of which will be passed to
* {@link #registerArchiveFormat(ArchiveFormatProvider)}.
*
*
*
*
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class FileFactory {
private static Logger logger;
/** All registered protocol providers. */
private static final MapZIP, registered to zip, jar, war, wal, wmz, xpi, ear, odt, ods and odp files.TAR, registered to tar, tar.gz, tgz, tar.bz2 and tbz2 files.GZIP, registered to gz files.BZip2, registered to bz2 files.ISO, registered to iso and nrg files.AR, registered to ar, a and deb files.LST, registered to lst files.RAR, registered to rar files.SEVENZIP, registered to 7z files.FileFactory is created.
*/
private FileFactory() {
}
/**
* Registers a new file protocol.
* protocol argument is expected to be the protocol identifier, without the trailing ://.
* For example, the identifier of the HTTP protocol would be http. This parameter's case is irrelevant,
* as it will be stored in all lower-case.
*
* null otherwise.
*/
public static ProtocolProvider registerProtocol(String protocol, ProtocolProvider provider) {
protocol = protocol.toLowerCase();
// create raw and archive file pools
synchronized (FILE_POOL_MAP) {
FILE_POOL_MAP.put(protocol, new FilePool());
}
// Special case for local file provider.
// Note that the local file provider is also added to the provider hashtable.
if (protocol.equals(FileProtocols.FILE)) {
localFileProvider = provider;
}
return protocolProviders.put(protocol, provider);
}
/**
* Unregisters the provider associated with the specified protocol.
*
* @param protocol identifier of the protocol whose provider should be unregistered.
* @return the provider that has been unregistered, or null if none.
*/
public static ProtocolProvider unregisterProtocol(String protocol) {
protocol = protocol.toLowerCase();
// Remove raw and archive file pools
synchronized (FILE_POOL_MAP) {
FILE_POOL_MAP.remove(protocol);
}
// Special case for local file provider
if (protocol.equals(FileProtocols.FILE)) {
localFileProvider = null;
}
return protocolProviders.remove(protocol);
}
/**
* Returns the protocol provider associated with the specified protocol identifier, or null if there
* is none.
*
* @param protocol identifier of the protocol whose provider should be retrieved.
* @return the protocol provider registered to the specified protocol identifier, or null if none.
*/
public static ProtocolProvider getProtocolProvider(String protocol) {
return protocolProviders.get(protocol.toLowerCase());
}
/**
* Returns true if the given protocol has a registered {@link ProtocolProvider}.
*
* @param protocol identifier of the protocol to test
* @return true if the given protocol has a registered {@link ProtocolProvider}.
*/
public static boolean isRegisteredProtocol(String protocol) {
return getProtocolProvider(protocol)!=null;
}
/**
* Returns an iterator on all known protocol names.
*
* nextElement() method will be string instances. These can
* then be passed to {@link #getProtocolProvider(String) getProtocolProvider} to retrieve the associated
* {@link ProtocolProvider}.
*
* @return an iterator on all known protocol names.
*/
public static IteratorArchiveFormatProvider.
*
* @param provider the ArchiveFormatProvider to register.
*/
public static void registerArchiveFormat(ArchiveFormatProvider provider) {
archiveFormatProvidersV.add(provider);
updateArchiveFormatProviderArray();
}
/**
* Removes a previously-registered ArchiveFormatProvider.
* FileFactory.unregisterArchiveFormat(FileFactory.getArchiveFormatProvider("file.zip"))
* will unregister the (first, if any) Zip provider.
*
* @param provider the ArchiveFormatProvider to unregister.
* @see #getArchiveFormatProvider(String)
*/
public static void unregisterArchiveFormat(ArchiveFormatProvider provider) {
int index = archiveFormatProvidersV.indexOf(provider);
if (index != -1) {
archiveFormatProvidersV.remove(index);
updateArchiveFormatProviderArray();
}
}
/**
* Updates the ArchiveFormatProvider array to reflect the contents of the Vector.
*/
private static void updateArchiveFormatProviderArray() {
archiveFormatProviders = new ArchiveFormatProvider[archiveFormatProvidersV.size()];
archiveFormatProvidersV.toArray(archiveFormatProviders);
}
/**
* Returns the first ArchiveFormatProvider that matches the specified filename, null
* if there is none. Note that if a filename matches the {@link java.io.FilenameFilter} of several registered
* providers, the first provider matching the filename will be returned.
*
* @param filename an archive filename that potentially matches one of the registered ArchiveFormatProvider
* @return the first ArchiveFormatProvider that matches the specified filename, null if there is none
*/
private static ArchiveFormatProvider getArchiveFormatProvider(String filename) {
if (filename == null || archiveFormatProviders == null) {
return null;
}
for (ArchiveFormatProvider provider : archiveFormatProviders) {
if (provider != null && provider.getFilenameFilter() != null && provider.getFilenameFilter().accept(filename)) {
return provider;
}
}
return null;
}
/**
* Returns an iterator on all known archive formats.
*
* @return an iterator on all known archive formats.
*/
public static Iteratornull if the file could not be created.
*
* @param absPath the absolute path to the file
* @return null if the given path is not absolute or incorrect (doesn't correspond to any file) or
* if something went wrong during file creation.
*/
public static AbstractFile getFile(String absPath) {
try {
return getFile(absPath, null);
} catch(IOException e) {
getLogger().info("Caught an exception (file {})", absPath, e);
return null;
}
}
/**
* Returns an instance of AbstractFile for the given absolute path.
*
* null if the file could not be created.
*
* @param absPath the absolute path to the file
* @param throwException if set to true, an IOException will be thrown if something went wrong during file creation
* @return null if the given path is not absolute or incorrect (doesn't correspond to any file)
* @throws java.io.IOException and throwException param was set to true.
* @throws AuthException if additional authentication information is required to create the file
*/
public static AbstractFile getFile(String absPath, boolean throwException) throws AuthException, IOException {
try {
return getFile(absPath, null);
} catch(IOException e) {
getLogger().info("Caught an exception", e);
if (throwException) {
throw e;
}
return null;
}
}
/**
* Returns an instance of AbstractFile for the given absolute path and use the given parent for the new file if
* not null. AbstractFile subclasses should as much as possible call this method rather than {@link #getFile(String)}
* because it is more efficient.
*
* @param absPath the absolute path to the file
* @param parent the returned file's parent
* @return an instance of AbstractFile for the specified absolute path.
* @throws java.io.IOException if something went wrong during file or file url creation.
* @throws AuthException if additional authentication information is required to create the file
*/
public static AbstractFile getFile(String absPath, AbstractFile parent) throws AuthException, IOException {
return getFile(FileURL.getFileURL(absPath), parent);
}
/**
* Returns an instance of AbstractFile for the given FileURL instance.
*
* @param fileURL the file URL
* @return the created file or null if something went wrong during file creation
*/
public static AbstractFile getFile(FileURL fileURL) {
try {
return getFile(fileURL, null);
} catch(IOException e) {
getLogger().info("Caught an exception", e);
return null;
}
}
/**
* Returns an instance of AbstractFile for the given FileURL instance.
*
* @param fileURL the file URL
* @param throwException if set to true, an IOException will be thrown if something went wrong during file creation
* @return the created file
* @throws java.io.IOException if something went wrong during file creation
*/
public static AbstractFile getFile(FileURL fileURL, boolean throwException) throws IOException {
try {
return getFile(fileURL, null);
} catch(IOException e) {
getLogger().info("Caught an exception", e);
if (throwException) {
throw e;
}
return null;
}
}
/**
* Shorthand for {@link #getFile(FileURL, AbstractFile, Authenticator, Object...)} called with the
* {@link #getDefaultAuthenticator() default authenticator}.
*
* @param fileURL the file URL representing the file to be created
* @param parent the parent AbstractFile to use as the created file's parent, can be null
* @return an instance of {@link AbstractFile} for the given {@link FileURL}.
* @throws java.io.IOException if something went wrong during file creation.
*/
public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Object... instantiationParams) throws IOException {
return getFile(fileURL, parent, defaultAuthenticator, instantiationParams);
}
/**
* Creates and returns an instance of AbstractFile for the given FileURL and uses the specified parent file (if any)
* as the created file's parent.
*
* null, no {@link Authenticator} will be used, not even the default one.
* @param parent the parent AbstractFile to use as the created file's parent, can be null
* @return an instance of {@link AbstractFile} for the given {@link FileURL}.
* @throws java.io.IOException if something went wrong during file creation.
*/
public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Authenticator authenticator, Object... instantiationParams) throws IOException {
String protocol = fileURL.getScheme();
if (!isRegisteredProtocol(protocol)) {
throw new IOException("Unsupported file protocol: " + protocol);
}
// Lookup the pool for an existing AbstractFile instance, only if there are no instantiationParams.
// If there are instantiationParams (the file was created by the AbstractFile implementation directly, that is
// by ls()), any existing file in the pool must be replaced with a new, more up-to-date one.
FilePool filePool = FILE_POOL_MAP.get(fileURL.getScheme().toLowerCase());
if (instantiationParams.length == 0) {
// Note: FileURL#equals(Object) and #hashCode() take into account credentials and properties and are
// trailing slash insensitive (e.g. '/root' and '/root/' URLS are one and the same)
AbstractFile file = filePool.get(fileURL);
if (file != null) {
return file;
}
}
String filePath = fileURL.getPath();
// For local paths under Windows (e.g. "/C:\temp"), remove the leading '/' character
if (OsFamily.WINDOWS.isCurrent() && FileProtocols.FILE.equals(protocol)) {
filePath = PathUtils.removeLeadingSeparator(filePath, "/");
}
String pathSeparator = fileURL.getPathSeparator();
PathTokenizer pt = new PathTokenizer(filePath, pathSeparator, false);
AbstractFile currentFile = null;
boolean lastFileResolved = false;
// Extract every filename from the path from left to right and for each of them, see if it looks like an archive.
// If it does, create the appropriate protocol file and wrap it with an archive file.
while (pt.hasMoreFilenames()) {
// Test if the filename's extension looks like a supported archive format...
// Note that the archive can also be a directory with an archive extension.
if (isArchiveFilename(pt.nextFilename())) {
// Remove trailing separator of file, some file protocols such as SFTP don't like trailing separators.
// On the contrary, directories without a trailing slash are fine.
String currentPath = PathUtils.removeTrailingSeparator(pt.getCurrentPath(), pathSeparator);
// Test if current file is an archive and if it is, create an archive entry file instead of a raw
// protocol file
if (currentFile == null || !currentFile.isArchive()) {
// create a fresh FileURL with the current path
FileURL clonedURL = (FileURL)fileURL.clone();
clonedURL.setPath(currentPath);
// Look for a cached file instance before creating a new one
currentFile = filePool.get(clonedURL);
if (currentFile == null) {
currentFile = wrapArchive(createRawFile(clonedURL, authenticator, instantiationParams));
// Add the intermediate file instance to the cache
filePool.put(clonedURL, currentFile);
}
lastFileResolved = true;
} else { // currentFile is an AbstractArchiveFile
// Note: wrapArchive() is already called by AbstractArchiveFile#createArchiveEntryFile()
AbstractFile tempEntryFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length()), pathSeparator));
if (tempEntryFile.isArchive()) {
currentFile = tempEntryFile;
lastFileResolved = true;
} else {
lastFileResolved = false;
}
// Note: don't cache the entry file
}
} else {
lastFileResolved = false;
}
}
// create last file if it hasn't been already (if the last filename was not an archive), same routine as above
// except that it doesn't wrap the file with an archive file
if (!lastFileResolved) {
// Note: DON'T strip out the trailing separator, as this would cause problems with root resources
String currentPath = pt.getCurrentPath();
if (currentFile == null || !currentFile.isArchive()) {
FileURL clonedURL = (FileURL)fileURL.clone();
clonedURL.setPath(currentPath);
// Note: no need to look a cached file instance, we have already looked for it at the very beginning.
currentFile = createRawFile(clonedURL, authenticator, instantiationParams);
// Add the final file instance to the cache
filePool.put(currentFile.getURL(), currentFile);
} else { // currentFile is an AbstractArchiveFile
currentFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length()), pathSeparator));
// Note: don't cache the entry file
}
}
// Reuse existing parent file instance if one was specified
if (parent != null)
currentFile.setParent(parent);
return currentFile;
}
private static AbstractFile createRawFile(FileURL fileURL, Authenticator authenticator, Object... instantiationParams) throws IOException {
String scheme = fileURL.getScheme().toLowerCase();
// Special case for local files to avoid provider hashtable lookup and other unnecessary checks
// (for performance reasons)
if (scheme.equals(FileProtocols.FILE)) {
if (localFileProvider == null) {
throw new IOException("Unknown file protocol: " + scheme);
}
return localFileProvider.getFile(fileURL, instantiationParams);
// Uncomment this line and comment the previous one to simulate a slow filesystem
//file = new DebugFile(file, 0, 50);
}
// Use the protocol hashtable for any other file protocol
else {
// If an Authenticator has been specified and the specified FileURL's protocol is authenticated and the
// FileURL doesn't contain any credentials, use it to authenticate the FileURL.
if (authenticator != null && fileURL.getAuthenticationType() != AuthenticationType.NO_AUTHENTICATION && !fileURL.containsCredentials()) {
authenticator.authenticate(fileURL);
}
// Finds the right file protocol provider
ProtocolProvider provider = getProtocolProvider(scheme);
if (provider == null) {
throw new IOException("Unknown file protocol: " + scheme);
}
return provider.getFile(fileURL, instantiationParams);
}
}
/**
* Returns a variation of the given filename, appending a pseudo-unique ID to the filename's prefix while keeping
* the same filename extension.
*
* @param filename base filename
*/
private static String getFilenameVariation(String filename) {
int lastDotPos = filename.lastIndexOf('.');
int len = filename.length();
String nameSuffix = "_" + System.currentTimeMillis() + (new Random().nextInt(10000));
if (lastDotPos == -1) {
filename += nameSuffix;
} else {
filename = filename.substring(0, lastDotPos) + nameSuffix + filename.substring(lastDotPos, len);
}
return filename;
}
/**
* Creates and returns a temporary local file using the desired filename. If a file with this name already exists
* in the temp directory, the filename's prefix (name without extension) will be appended an ID. The filename's
* extension will however always be preserved.
*
* true, the temporary file will be deleted upon normal termination of the JVM
* @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds
* to a registered archive format.
* @throws IOException if an error occurred while instantiating the temporary file. This should not happen under
* normal circumstances.
*/
public static AbstractFile getTemporaryFile(String desiredFilename, boolean deleteOnExit) throws IOException {
if (desiredFilename == null || desiredFilename.isEmpty()) {
desiredFilename = "temp";
}
// Attempt to use the desired name
AbstractFile tempFile = TEMP_DIRECTORY.getDirectChild(desiredFilename);
if (tempFile.exists()) {
tempFile = TEMP_DIRECTORY.getDirectChild(getFilenameVariation(desiredFilename));
}
if (deleteOnExit) {
((java.io.File)tempFile.getUnderlyingFileObject()).deleteOnExit();
}
return tempFile;
}
/**
* Creates a temporary file with a default filename. This method is a shorthand for
* {@link #getTemporaryFile(String, boolean)} called with a null name.
*
* @param deleteOnExit if true, the temporary file will be deleted upon normal termination of the JVM
* @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds
* to a registered archive format.
* @throws IOException if an error occurred while instantiating the temporary file. This should not happen under
* normal circumstances.
*/
public static AbstractFile getTemporaryFile(boolean deleteOnExit) throws IOException {
return getTemporaryFile(null, deleteOnExit);
}
/**
* Returns the temporary folder, i.e. the parent folder of temporary files returned by
* {@link #getTemporaryFile(String, boolean)}.
*
* @return the temporary folder
*/
public static AbstractFile getTemporaryFolder() {
return TEMP_DIRECTORY;
}
/**
* Returns true if the given filename's extension matches one of the registered archive formats.
*
* @param filename the filename to test
* @return true if the specified filename is a known archive file name, false otherwise.
*/
public static boolean isArchiveFilename(String filename) {
if (archiveExtensions == null) {
if (archiveFormatProviders == null) {
return false;
}
archiveExtensions = new HashSet<>();
for (ArchiveFormatProvider provider : archiveFormatProviders) {
if (provider != null) {
String[] extensions = provider.getFileExtensions();
for (String ext : extensions) {
String extWithoutDot = ext.startsWith(".") ? ext.substring(1) : ext;
archiveExtensions.add(extWithoutDot.toLowerCase());
}
}
}
}
String ext = AbstractFile.getExtension(filename);
return ext != null && archiveExtensions.contains(ext.toLowerCase());
//return getArchiveFormatProvider(filename) != null;
}
/**
* Tests if the given file's extension matches that of one of the registered archive formats.
* If it does, a corresponding {@link AbstractArchiveFile} instance is created on top of the provided file
* and returned. If it doesn't, the provided AbstractFile instance is simply returned.
* Method object.
* @return the {@link AbstractFile} method corresponding to this file operation.
*/
public Method getCorrespondingMethod(Class extends AbstractFile> c) {
try {
switch(this) {
case READ_FILE:
return c.getMethod("getInputStream");
case RANDOM_READ_FILE:
return c.getMethod("getRandomAccessInputStream");
case WRITE_FILE:
return c.getMethod("getOutputStream");
case APPEND_FILE:
return c.getMethod("getAppendOutputStream");
case RANDOM_WRITE_FILE:
return c.getMethod("getRandomAccessOutputStream");
case CREATE_DIRECTORY:
return c.getMethod("mkdir");
case LIST_CHILDREN:
return c.getMethod("ls");
case CHANGE_DATE:
return c.getMethod("setLastModifiedDate", Long.TYPE);
case GET_BLOCKSIZE:
return c.getMethod("getBlocksize");
case GET_REPLICATION:
return c.getMethod("getReplication");
case CHANGE_REPLICATION:
return c.getMethod("changeReplication", Short.TYPE);
case CHANGE_PERMISSION:
return c.getMethod("changePermission", Integer.TYPE, Integer.TYPE, Boolean.TYPE);
case DELETE:
return c.getMethod("delete");
case RENAME:
return c.getMethod("renameTo", AbstractFile.class);
case COPY_REMOTELY:
return c.getMethod("copyRemotelyTo", AbstractFile.class);
case GET_FREE_SPACE:
return c.getMethod("getFreeSpace");
case GET_TOTAL_SPACE:
return c.getMethod("getTotalSpace");
default:
// This should never be reached, unless method signatures have changed and this method hasn't been updated.
LOGGER.warn("this line should not have been executed");
return null;
}
} catch (Exception e) {
// Should never happen, unless method signatures have changed and this method hasn't been updated.
LOGGER.warn("this line should not have been executed", e);
return null;
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/FilePermissions.java
================================================
package com.mucommander.commons.file;
/**
* FilePermissions is an interface that represents the permissions of an {@link com.mucommander.commons.file.AbstractFile}.
* The actual permission values can be retrieved by the methods inherited from the
* {@link com.mucommander.commons.file.PermissionBits} interface. The permissions mask returned by {@link #getMask()} allows
* to determine which permission bits are significant, i.e. should be taken into account. That way, certain
* {@link AbstractFile} implementations that have limited permissions support can set those supported permission bits
* while making it clear that other bits should be ignored, and not simply be considered as being disabled.
* For instance, a file implementation with support for the sole 'user' permissions (read/write/execute) will return a
* mask whose int value is 448 (700 octal).
*
*
* scheme://[login[:password]@]host[:port][/path][?query]
*
*
* Instantiation
* java.net.URL and java.net.URI classes, FileURL instances are mutable --
* all URL parts can be freely modified. FileURL instances can also be cloned using the standard {@link #clone()} method.
*
* Handlers and Scheme-specific attributes
*
*
* These attribute values are provided by the {@link SchemeHandler} registered with the scheme, if any.
* registerHandler and
* unregisterHandler.
* scheme://). Those locations can be system-dependent,
* local and absolute paths, or UNC paths. These paths are turned by the parser into an equivalent, fully-qualified URL.
*
* Properties
* Limitations
*
*
* Some of these limitations will be addressed in upcoming revisions of this class.
*
* @see SchemeHandler
* @see SchemeParser
* @author Maxence Bernard
*/
public class FileURL implements Cloneable {
// Todo: add support for the fragment part
// Todo: add percent encoding/decoding
/** Handler instance that provides the scheme-specific features of this FileURL */
private SchemeHandler handler;
/** Scheme part */
private String scheme;
/** Port part, -1 if this URL has none */
private int port = -1;
/** Host part, null if this URL has none */
private String host;
/** Path part */
private String path;
/** Filename, extracted from the path, null if the path has none */
private String filename;
/** Query part, null if this URL has none */
private String query;
/** Properties, null if none have been set thus far */
private MapMalformedURLException
* if the specified location is not a valid URL or path and cannot be resolved. The {@link SchemeParser parser}
* of the {@link SchemeHandler handler} registered for the location's scheme is used to parse the given location.
* If the scheme specified in the location does not have a specific handler, or if the location does not contain a
* scheme (i.e. is local or UNC path, not a URL) then the default handler's parser is used.
*
* @param location the URL or path for which to get a FileURL instance
* @throws MalformedURLException if the specified string isn't a valid URL, according to the scheme's parser used
* @return a FileURL corresponding to the given location
*/
public static FileURL getFileURL(String location) throws MalformedURLException {
if (location == null) {
return null;
}
int schemeDelimPos = location.indexOf("://");
SchemeHandler handler;
if (schemeDelimPos == -1) {
// No scheme: the location is a local or UNC path, not a URL
handler = getDefaultHandler();
} else {
handler = getSchemeHandler(location.substring(0, schemeDelimPos));
}
FileURL fileURL = new FileURL(handler);
try {
handler.getParser().parse(location, fileURL);
} catch (Exception e) {
// Catch any unexpected exception thrown by the SchemeParser and turn it into a MalformedURLException
// with a specific error message.
if (e instanceof MalformedURLException) {
throw (MalformedURLException) e;
}
throw new MalformedURLException("URL parser error");
}
return fileURL;
}
/**
* Returns the handler registered the specified scheme if there is one, the default handler otherwise.
*
* @param scheme the scheme for which to return a handler
* @return a handler for the specified scheme
*/
private static SchemeHandler getSchemeHandler(String scheme) {
SchemeHandler handler = getRegisteredHandler(scheme);
if (handler == null) {
return getDefaultHandler();
}
return handler;
}
/**
* Returns the SchemeHandler instance that provides the scheme-specific features of this FileURL.
*
* @return the SchemeHandler instance that provides the scheme-specific features of this FileURL
*/
public SchemeHandler getHandler() {
return handler;
}
/**
* Sets the SchemeHandler that provides the scheme-specific features of this FileURL.
* SchemeHandler instance that provides the scheme-specific features of this FileURL
*/
public void setHandler(SchemeHandler handler) {
this.handler = handler;
}
/**
* Registers a handler for the specified scheme, replacing any handler previously registered
* for the same scheme.
*
* @param scheme the scheme to associate the handler with (case-insensitive)
* @param handler the new handler in charge of the scheme
*/
static void registerHandler(String scheme, SchemeHandler handler) {
handlers.put(scheme.toLowerCase(), handler);
}
/**
* Removes any handler associated with the specified scheme, leaving the default handler in charge of the scheme.
* This method has no effect if there is no handler registered for the scheme.
*
* @param scheme the scheme to remove the handler for
*/
static void unregisterHandler(String scheme) {
handlers.remove(scheme.toLowerCase());
}
/**
* Returns the handler registered for the specified scheme, null if there isn't any.
*
* @param scheme the scheme for which to return the handler
* @return the handler registered for the specified scheme
*/
public static SchemeHandler getRegisteredHandler(String scheme) {
return handlers.get(scheme.toLowerCase());
}
/**
* Returns the default handler, which handles schemes which do not have a specific handler.
* The returned instance is a {@link DefaultSchemeHandler} created with the no-arg constructor.
*
* @return the default handler
*/
static SchemeHandler getDefaultHandler() {
return DEFAULT_HANDLER;
}
/**
* Extracts the filename from the given path and returns it, or null if the path does not contain
* a filename.
*
* @param path the path from which to extract a filename
* @param separator the path separator
* @return the filename extracted from the given path, null if the path doesn't contain any
*/
public static String getFilenameFromPath(String path, String separator) {
if (path.isEmpty() || path.equals("/")) {
return null;
}
// Remove any trailing separator
path = PathUtils.removeTrailingSeparator(path, separator);
if (!separator.equals("/")) {
path = PathUtils.removeLeadingSeparator(path, "/");
}
// Extract filename
int pos = path.lastIndexOf(separator);
if (pos < 0) {
return null;
}
return path.substring(pos+1);
}
/**
* Returns the scheme part of this URL. The returned scheme may never be null.
*
* @return the scheme part of this FileURL.
* @see #setScheme(String)
*/
public String getScheme() {
return scheme;
}
/**
* Sets the scheme part of this URL. An IllegalArgumentException will be thrown if the specified scheme
* is null or an empty string.
* null if it doesn't contain any.
*
* @return the host part of this URL.
* @see #setHost(String)
*/
public String getHost() {
return host;
}
/**
* Sets the host part of this URL, null for no host.
*
* @param host new host part of this URL.
* @see #getHost()
*/
public void setHost(String host) {
this.host = host;
urlModified();
}
/**
* Returns the port part of this URL, -1 if none was specified in the URL.
*
* @return the port part of this URL, -1 if there isn't any.
* @see #setPort(int)
* @see #getDefaultHandler()
*/
public int getPort() {
return port;
}
/**
* Sets the port part of this URL, -1 for no specific port.
*
* @param port new port part of this URL.
* @see #getPort()
* @see #getDefaultHandler()
*/
public void setPort(int port) {
this.port = port;
urlModified();
}
/**
* Returns this scheme's standard port, -1 if the scheme doesn't have any.
* If this URL doesn't have a specific port part, the return value should be considered as being this URL's port.
*
* getHandler().getStandardPort().
*
* @return the scheme's standard port
* @see #getPort()
*/
public int getStandardPort() {
return handler.getStandardPort();
}
/**
* Returns the login part of this URL, null if there isn't any.
*
* @return the login part of this URL, null if there isn't any
* @see #getCredentials()
*/
public String getLogin() {
return credentials==null?null:credentials.getLogin();
}
/**
* Returns the password part of this URL, null if there isn't any.
*
* @return the password part of this URL, null if there isn't any
* @see #getCredentials()
*/
public String getPassword() {
return credentials==null?null:credentials.getPassword();
}
/**
* Returns the type of authentication used by the scheme's file protocol. The returned value is one of the constants
* defined in the {@link AuthenticationType} enum.
*
* getHandler().getAuthenticationType().
*
* @return the type of authentication used by the scheme's file protocol
*/
public AuthenticationType getAuthenticationType() {
return handler.getAuthenticationType();
}
/**
* Returns true if this URL contains credentials, i.e. a login and/or password part. If true is
* returned, {@link #getCredentials()} will return a non-null value.
*
* @return true if this URL contains credentials, false otherwise.
*/
public boolean containsCredentials() {
return credentials!=null;
}
/**
* Returns the credentials (login and password) contained by this URL, wrapped in an {@link Credentials} object.
* Returns null if this URL doesn't have a login or password part.
*
* null if this URL doesn't have a login or password part.
* @see #setCredentials(Credentials)
* @see #getAuthenticationType()
*/
public Credentials getCredentials() {
return credentials;
}
/**
* Sets the login and password parts of this URL. Any credentials contained by this FileURL will be replaced.
* null can be passed to discard existing credentials.
*
* null if the scheme doesn't have any.
* getHandler().getGuestCredentials().
*
* @return the scheme's guest credentials, null if the scheme doesn't have any
*/
public Credentials getGuestCredentials() {
return handler.getGuestCredentials();
}
/**
* Returns the path part of this URL. The returned value will never be null and always start with a
* leading '/' character.
*
* @return the path part of this URL.
* @see #setPath(String)
*/
public String getPath() {
return path;
}
/**
* Sets the path part of this URL. The specified path cannot be null and must start with a leading
* '/' character. If the specified path value is null, then the path will be set to "/".
* If the path does not start with a leading separator, one will be added.
*
* @param path new path part of this URL
* @see #getPath()
*/
public void setPath(String path) {
if (path == null || path.isEmpty()) {
path = "/";
}
if (!path.startsWith("/")) {
path = "/" + path;
}
this.path = path;
// Extract new filename from path
this.filename = getFilenameFromPath(path, getPathSeparator());
urlModified();
}
/**
* Returns this scheme's path separator, which serves as a delimiter for path fragments. For most schemes, this is
* the forward slash character.
*
* getHandler().getPathSeparator().
*
* @return this scheme's path separator
*/
public String getPathSeparator() {
return handler.getPathSeparator();
}
/**
* Returns the parent of this URL according to its path, null if this URL has no parent (its path is "/").
* null, even if this URL had one.
*
* null if it doesn't have one.
*/
public FileURL getParent() {
// If path equals '/', url has no parent
if (!(path.equals("/") || path.isEmpty())) {
String separator = getPathSeparator();
// Remove any trailing separator
String parentPath = path.endsWith(separator)?path.substring(0, path.length()-separator.length()):path;
// Resolve parent folder's path and reconstruct parent URL
int lastSeparatorPos = parentPath.lastIndexOf(separator);
if (lastSeparatorPos >= 0) {
FileURL parentURL = new FileURL(handler);
parentURL.scheme = scheme;
parentURL.host = host;
parentURL.port = port;
parentURL.path = parentPath.substring(0, lastSeparatorPos+1); // Keep trailing slash
parentURL.filename = getFilenameFromPath(parentURL.path, separator);
// Set same credentials for parent, (if any)
// Note: Credentials are immutable.
parentURL.credentials = credentials;
// Copy properties to parent (if any)
if (properties != null)
parentURL.properties = new HashMap<>(properties);
return parentURL;
}
}
return null; // URL has no parent
}
/**
* Returns the authentication realm corresponding to this URL, i.e. the base location throughout which credentials
* can be used. Any property contained by the specified FileURL will be carried over in the returned FileURL.
* On the contrary, credentials will not be copied, the returned URL always has no credentials.
*
* getHandler().getRealm(this).
*
* @return this url's authentication realm
*/
public FileURL getRealm() {
return handler.getRealm(this);
}
/**
* Returns the filename of this URL , null if doesn't have one (e.g. if the path is "/").
* setFilename as the filename is simply extrapolated from the path.
* Use {@link #setPath(String)} to change the path and its filename.
*
* @return the filename of this URL, null if it doesn't have one.
* @see #setPath(String)
*/
public String getFilename() {
return filename;
}
/**
* Returns the query part of this URL if it has one, null otherwise.
*
* @return the query part of this URL if it has one, null otherwise
* @see #setQuery(String)
*/
public String getQuery() {
return query;
}
/**
* Sets the query part of this URL, null for no query part.
*
* @param query new query part of this URL, null for no query part
* @see #getQuery()
*/
public void setQuery(String query) {
this.query = query;
urlModified();
}
/**
* Returns the value corresponding to the given property name, null if the property has no value.
*
* @param name name of the property whose value is to be retrieved
* @return the value associated with the specified property name, null if it has no value
* @see #setProperty(String,String)
*/
public String getProperty(String name) {
return properties==null?null:properties.get(name);
}
/**
* Sets the given property (name/value pair) in the FileURL instance. A null property value has the
* effect of removing the property.
*
* @param name name of the property to set
* @param value value of the property
* @see #getProperty(String)
*/
public void setProperty(String name, String value) {
// create the property map only when a property is set for the first time
if (properties == null) {
properties = new HashMap<>();
}
if (value == null) {
properties.remove(name);
} else {
properties.put(name, value);
}
urlModified();
}
/**
* Returns an Enumeration of all property names this FileURL contains.
*
* @return an Enumeration of all property names this FileURL contains
*/
public Settrue, the login and password parts (if any) will be included in the
* returned URL.
* @param maskPassword if true and the includeCredentials parameter is also true, the password's
* characters (if any) will be replaced by '*' characters. This allows a URL containing credentials to be displayed
* to the end user without revealing the actual password.
* @return a string representation of this FileURL
*/
public String toString(boolean includeCredentials, boolean maskPassword) {
StringBuilder sb = new StringBuilder(scheme);
sb.append("://");
if (includeCredentials && credentials != null) {
sb.append(URLEncoder.encode(credentials.getLogin(), StandardCharsets.UTF_8));
String password = credentials.getPassword();
if (!password.isEmpty()) {
sb.append(':');
if (maskPassword) {
sb.append(credentials.getMaskedPassword());
} else {
sb.append(URLEncoder.encode(password, StandardCharsets.UTF_8));
}
}
sb.append('@');
}
if (host != null) {
sb.append(host);
}
// Set the port only if it has a value that is different from the standard port
if (port != -1 && port != handler.getStandardPort()) {
sb.append(':');
sb.append(port);
}
if (host != null || !path.equals("/")) { // Test to avoid URLs like 'smb:///'
if (path.startsWith("/")) {
sb.append(path);
} else {
// Add a leading '/' if path doesn't already start with one, needed for scheme paths that are not
// forward slash-separated
sb.append('/');
sb.append(path);
}
}
if (query != null) {
sb.append('?');
sb.append(query);
}
return sb.toString();
}
/**
* Returns a String representation of this FileURL, including the login and password parts (credentials) only if
* requested.
*
* @param includeCredentials if true, the login and password parts (if any) will be included in the
* returned URL.
* @return a string representation of this FileURL.
*/
public String toString(boolean includeCredentials) {
return toString(includeCredentials, false);
}
/**
* Creates and returns a java.net.URL referring to the same location as this FileURL.
* The java.net.URL is created from the string representation of this FileURL.
* Thus, any credentials this FileURL contains are preserved, but properties are lost.
*
* URL uses an {@link AbstractFile} to access the associated resource.
* An {@link AbstractFile} instance is created by the underlying URLConnection when the URL is
* connected.
*
* java.net.URL.
*
* @return a java.net.URL referring to the same location as this FileURL
* @throws MalformedURLException if the java.net.URL could not parse the location of this FileURL
*/
public URL getJavaNetURL() throws MalformedURLException {
return new URL(null, toString(true), new CompatURLStreamHandler());
}
/**
* Returns true if the scheme part of this URL and the given URL are equal.
* The comparison is case-sensitive.
*
* @param url the URL to test for scheme equality
* @return true if the scheme part of this URL and the given URL are equal
*/
public boolean schemeEquals(FileURL url) {
return this.scheme.equalsIgnoreCase(url.scheme);
}
/**
* Returns true if the host part of this URL and the given URL are equal.
* The comparison is case-insensitive.
*
* @param url the URL to test for host equality
* @return true if the host part of this URL and the given URL are equal
*/
public boolean hostEquals(FileURL url) {
// Note: StringUtils#equals is null-safe
return StringUtils.equals(this.host, url.host, false);
}
/**
* Returns true if the port of this URL and the given URL's are equal. Ports are said to be equal if
* the values returned by {@link #getPort()} are equal, or if both URLs have the same standard port
* (as returned by {@link #getStandardPort()} and one of the port value is -1 (undefined) and the other
* is the standard port.
*
* @param url the URL to test for port equality
* @return true if the port of this URL and the given one are equal
*/
public boolean portEquals(FileURL url) {
int port1 = this.port;
int port2 = url.port;
int standardPort = getStandardPort();
return port1==port2 ||
(standardPort==url.getStandardPort() && ((port1==-1 && port2==standardPort || (port2==-1 && port1==standardPort))));
}
/**
* Returns true if the path of this URL and the given URL are equal. The comparison is case-sensitive.
* If the sole difference between two paths is a trailing path separator (and both URLs have the same path separator),
* they will be considered as equal.
* For example, /path and /path/ are considered equal, assuming the path separator is '/'.
*
* java.lang.String#equals(Object) to compare URL paths,
* which in some rare cases may return false for non-ascii/Unicode paths that have the same written
* representation but are not equal according to java.lang.String#equals(Object). Handling such cases
* would require a locale-aware String comparison which is not an option here.
*
* @param url the URL to test for path equality
* @return true if the path of this URL and the given URL are equal
*/
public boolean pathEquals(FileURL url) {
boolean isCaseSensitiveOS = !(OsFamily.WINDOWS.isCurrent() || OsFamily.OS_2.isCurrent());
String path1 = isCaseSensitiveOS ? this.getPath() : this.getPath().toLowerCase();
String path2 = isCaseSensitiveOS ? url.getPath() : url.getPath().toLowerCase();
if (path1.equals(path2))
return true;
String separator = getPathSeparator();
if (separator.equals(url.getPathSeparator())) {
int separatorLen = separator.length();
int len1 = path1.length();
int len2 = path2.length();
// If the difference between the 2 strings is just a trailing path separator, we consider the paths as equal
if (Math.abs(len1-len2) == separatorLen && (len1 > len2 ? path1.startsWith(path2) : path2.startsWith(path1))) {
String diff = len1>len2 ? path1.substring(len1-separatorLen) : path2.substring(len2-separatorLen);
return separator.equals(diff);
}
}
return false;
}
/**
* Returns true if the query part of this URL and the given URL are equal.
* The comparison is case-sensitive.
*
* @param url the URL to test for query equality
* @return true if the query part of this URL and the given URL are equal
*/
public boolean queryEquals(FileURL url) {
return StringUtils.equals(this.query, url.query, true);
}
/**
* Returns true if the credentials (login and password) of this URL and the given URL are equal.
* The comparison is case-sensitive.
*
* @param url the URL to test for credentials equality
* @return true if the credentials of this URL and the given URL are equal
*/
public boolean credentialsEquals(FileURL url) {
Credentials creds1 = this.credentials;
Credentials creds2 = url.credentials;
return (creds1 == null && creds2 == null)
|| (creds1 != null && creds1.equals(creds2, true))
|| (creds2 != null && creds2.equals(creds1, true));
}
/**
* Returns true if the properties contained by this URL and the given URL are equal.
* The comparison of each property is case-sensitive.
*
* @param url the URL to test for properties equality
* @return true if the properties contained by this URL and the given URL are equal
*/
private boolean propertiesEquals(FileURL url) {
return (this.properties == null && url.properties == null)
|| (this.properties != null && this.properties.equals(url.properties))
|| (url.properties != null && url.properties.equals(this.properties));
}
////////////////////////
// Overridden methods //
////////////////////////
/**
* Returns a String representation of this FileURL, without including the login and password parts it may have.
*/
public String toString() {
return toString(false);
}
/**
* Returns a clone of this FileURL. The returned instance can safely be modified without any impact on this FileURL
* or any previously cloned URL.
*/
@Override
public Object clone() {
// create a new FileURL return it, instead of using Object.clone() which is probably way slower;
// most FileURL fields are immutable and as such reused in cloned instance
FileURL clonedURL = new FileURL(handler);
// Immutable fields
clonedURL.scheme = scheme;
clonedURL.host = host;
clonedURL.port = port;
clonedURL.path = path;
clonedURL.filename = filename;
clonedURL.query = query;
clonedURL.credentials = credentials; // Note: Credentials are immutable.
// Mutable fields
if (properties != null) { // Copy properties (if any)
clonedURL.properties = new HashMap<>(properties);
}
// Caches
clonedURL.hashCode = hashCode;
return clonedURL;
}
/**
* This method is equivalent to calling {@link #equals(Object, boolean, boolean)} with credentials and properties
* comparisons enabled.
*
* @param o object to compare against this FileURL instance.
* @return true if both FileURL instances are equal.
*/
public boolean equals(Object o) {
return equals(o, true, true);
}
/**
* Tests the specified FileURL for equality with this FileURL. false is systematically returned if the
* specified object is not a FileURL instance or is null.
* FileURL instances are said to be equal if:
*
*
* http://mucommander.com:80/
* and http://mucommander.com/ are considered equal./path and /path/ are considered equal
* (assuming the path separator is '/').
* Likewise, properties are compared only if requested: the comparison of all properties is case-sensitive.
*
* @param o object to compare against this FileURL instance
* @param compareCredentials if true, the login and password parts of both FileURL need to be
* equal (case-sensitive) for the FileURL instances to be equal
* @param compareProperties if true, all properties need to be equal (case-sensitive) in both
* FileURL for them to be equal
* @return true if both FileURL instances are equal
*/
public boolean equals(Object o, boolean compareCredentials, boolean compareProperties) {
if (!(o instanceof FileURL)) {
return false;
}
FileURL url = (FileURL)o;
return pathEquals(url) // Compare the path first as it is the most likely to be different
&& schemeEquals(url)
&& hostEquals(url)
&& portEquals(url)
&& queryEquals(url)
&& (!compareCredentials || credentialsEquals(url))
&& (!compareProperties || propertiesEquals(url));
}
/**
* This method is overridden to return a hash code that takes into account the behavior of {@link FileURL#equals(Object)},
* so that url1.equals(url2) implies url1.hashCode()==url2.hashCode().
*/
public int hashCode() {
if (hashCode == 0) {
String separator = handler.getPathSeparator();
// #equals(Object) is trailing separator insensitive, so the hashCode must be trailing separator invariant
int h = PathUtils.getPathHashCode(path, separator);
h = 31* h + scheme.toLowerCase().hashCode();
h = 31* h + (port==-1?handler.getStandardPort():port);
if (host != null)
h = 31* h + host.toLowerCase().hashCode();
if (query != null)
h = 31* h + query.hashCode();
if (credentials != null)
h = 31* h + credentials.hashCode();
if (properties != null)
h = 31* h + properties.hashCode();
// Cache the value until for as long as this instance is not modified
hashCode = h;
}
return hashCode;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/GroupedPermissionBits.java
================================================
package com.mucommander.commons.file;
/**
* GroupedPermissionBits is an implementation of {@link com.mucommander.commons.file.PermissionBits} using a given UNIX-style
* permission int: {@link #getIntValue()} returns the specified int, and {@link #getBitValue(int, int)} isolates a
* specified value.
*
* @see com.mucommander.commons.file.IndividualPermissionBits
* @author Maxence Bernard
*/
public class GroupedPermissionBits implements PermissionBits {
/** UNIX-style permission int */
protected int permissions;
/**
* Creates a new GroupedPermissionBits using the specified UNIX-style permission int. The int can be created
* by combining (binary OR and shift) values defined in {@link com.mucommander.commons.file.PermissionTypes} and
* {@link com.mucommander.commons.file.PermissionAccesses}.
*
* @param permissions a UNIX-style permission int.
*/
public GroupedPermissionBits(int permissions) {
this.permissions = permissions;
}
@Override
public int getIntValue() {
return permissions;
}
@Override
public boolean getBitValue(int access, int type) {
return (permissions & (type << (access*3))) != 0;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/IndividualPermissionBits.java
================================================
package com.mucommander.commons.file;
/**
* IndividualPermissionBits is a partial implementation of {@link com.mucommander.commons.file.PermissionBits} that relies
* on {@link #getBitValue(int, int)}: the implementation of {@link #getIntValue()} calls getBitValue()
* sequentially for each permission bit, 9 times in total.
*
* @see com.mucommander.commons.file.GroupedPermissionBits
* @author Maxence Bernard
*/
public abstract class IndividualPermissionBits implements PermissionBits {
public IndividualPermissionBits() {
}
@Override
public int getIntValue() {
int bitShift = 0;
int perms = 0;
for(int a=PermissionAccesses.OTHER_ACCESS; a<=PermissionAccesses.USER_ACCESS; a++) {
for(int p=PermissionTypes.EXECUTE_PERMISSION; p<=PermissionTypes.READ_PERMISSION; p=p<<1) {
if(getBitValue(a, p))
perms |= (1<null
* if the type is unknown (unknown or no extension) or if the file is a folder.
*
* @param file the given file
*
* @return the MIME type
*/
public static String getMimeType(AbstractFile file) {
if (file.isDirectory()) {
return null;
}
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos < 0) {
return null;
}
return MIME_TYPES.get(name.substring(pos+1).toLowerCase());
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/MutableFileAttributes.java
================================================
package com.mucommander.commons.file;
/**
* This interface extends FileAttributes to add attribute getters. Refer to {@link FileAttributes}'s
* documentation for more information about attributes.
*
* true if the file exists physically on the underlying filesystem
*/
void setExists(boolean exists);
/**
* Sets the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
*
* @param date the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
*/
void setDate(long date);
/**
* Sets the file's size in bytes.
*
* @param size the file's size in bytes
*/
void setSize(long size);
/**
* Specifies whether the file is a directory or a regular file.
*
* @param directory true for directory, false for regular file
*/
void setDirectory(boolean directory);
/**
* Sets the file's permissions.
*
* @param permissions the file's permissions
*/
void setPermissions(FilePermissions permissions);
/**
* Sets the file's owner.
*
* @param owner the file's owner
*/
void setOwner(String owner);
/**
* Sets the file's group.
*
* @param group the file's owner
*/
void setGroup(String group);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/PathCanonizer.java
================================================
package com.mucommander.commons.file;
/**
* PathCanonizer is an interface that defines a single {@link #canonize(String)} method that returns the canonical
* representation of a given path. This interface is used by {@link SchemeParser} implementations to perform
* scheme-specific path canonization, independently of the actual URL parsing.
*
* @see DefaultSchemeParser
* @author Maxence Bernard
*/
public interface PathCanonizer {
/**
* Returns a canonical representation of the given path.
*
* @param path path to canonize
* @return a canonical representation of the given path.
*/
String canonize(String path);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/PermissionAccesses.java
================================================
package com.mucommander.commons.file;
/**
* This interface defines constants fields used for designating the three different permission accesses:
* {@link #USER_ACCESS}, {@link #GROUP_ACCESS} and {@link #OTHER_ACCESS}. Their actual int values represent the number
* of 3-bit left shifts (<< operator) needed to represent a particular
* {@link com.mucommander.commons.file.PermissionTypes permission type} in a UNIX-style permission int. To illustrate,
* the 'read' permission (value = 4) for the 'user' access (value = 2) is represented in a UNIX-style permission int as:
* 4 << 3*2 = 256 (400 octal).
*
* @see com.mucommander.commons.file.PermissionTypes
* @author Maxence Bernard
*/
public interface PermissionAccesses {
/** Designates the 'other' permission access. */
int OTHER_ACCESS = 0;
/** Designates the 'group' permission access. */
int GROUP_ACCESS = 1;
/** Designates the 'user' permission access. */
int USER_ACCESS = 2;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/PermissionBits.java
================================================
package com.mucommander.commons.file;
/**
* This interface provides methods to access file permissions, for every combination of types and accesses defined
* in {@link com.mucommander.commons.file.PermissionTypes} and {@link com.mucommander.commons.file.PermissionAccesses} respectively.
* This interface also defines constants for commonly used permission values.
*
*
*
*
* @see com.mucommander.commons.file.GroupedPermissionBits
* @see com.mucommander.commons.file.IndividualPermissionBits
* @author Maxence Bernard
*/
public interface PermissionBits {
/** read/write/execute permissions set for user/group/other (777 octal) */
int FULL_PERMISSION_INT = 511;
/** read/write/execute permissions set for user/group/other (777 octal) */
PermissionBits FULL_PERMISSION_BITS = new GroupedPermissionBits(FULL_PERMISSION_INT);
/** read/write/execute permissions cleared for user/group/other (0) */
int EMPTY_PERMISSION_INT = 0;
/** read/write/execute permissions cleared for user/group/other (0) */
PermissionBits EMPTY_PERMISSION_BITS = new GroupedPermissionBits(EMPTY_PERMISSION_INT);
/**
* Returns the value of all the permission bits (9 in total) in a UNIX-style permission int. Each of the permission
* bits can be isolated by comparing them against the values defined in {@link com.mucommander.commons.file.PermissionTypes}
* and {@link com.mucommander.commons.file.PermissionAccesses}.
*
* @return the value of all the permission bits (9 in total) in a UNIX-style permission int
*/
int getIntValue();
/**
* Returns the value of a specific permission bit: getIntValue() method by relying on getBitValue() and querying it sequentially for every
* permission bit.true if the permission is set, false
* if it isn't.
*
* @param access one of the values defined in {@link com.mucommander.commons.file.PermissionAccesses}
* @param type one of the values defined in {@link com.mucommander.commons.file.PermissionTypes}
* @return true if the permission is set, false if it isn't
*/
boolean getBitValue(int access, int type);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/PermissionTypes.java
================================================
package com.mucommander.commons.file;
/**
* This interface defines constants fields used for designating the three different permission types:
* {@link #READ_PERMISSION}, {@link #WRITE_PERMISSION} and {@link #EXECUTE_PERMISSION}. Their actual value represent
* the bit to be set and left-shifted with the desired {@link com.mucommander.commons.file.PermissionAccesses permission access}
* in a UNIX-style permission int.
*
* @see PermissionAccesses
* @author Maxence Bernard
*/
public interface PermissionTypes {
/** Designates the 'execute' permission. */
int EXECUTE_PERMISSION = 1;
/** Designates the 'write' permission. */
int WRITE_PERMISSION = 2;
/** Designates the 'read' permission. */
int READ_PERMISSION = 4;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ProtocolFile.java
================================================
package com.mucommander.commons.file;
/**
* Super class of all file protocol implementations (by opposition to {@link AbstractArchiveFile archive file}
* implementations).
*
* @see ProtocolProvider
* @author Maxence Bernard
*/
public abstract class ProtocolFile extends AbstractFile {
protected ProtocolFile(FileURL url) {
super(url);
}
/**
* This implementation always returns false.
*
* @return false, always
*/
@Override
public boolean isArchive() {
return false;
}
/*@Override
public boolean canGetReplication() {
return false;
}
@Override
public boolean canGetBlocksize() {
return false;
}
@Override
public short getReplication() {
return 0;
}
@Override
public long getBlocksize() {
return 0;
}
@Override
public void changeReplication(short replication) throws IOException {
}*/
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ProtocolProvider.java
================================================
package com.mucommander.commons.file;
import java.io.IOException;
/**
* This interface allows {@link FileFactory} to instantiate {@link AbstractFile} implementations.
* AbstractFile that matches the specified URL.
* @param url URL to map as an AbstractFile.
* @param instantiationParams file implementation-specific parameters used for instantiating the
* {@link AbstractFile} implementation. Those parameters are used when creating file instances within
* the AbstractFile implementation.
* @return a new instance of AbstractFile that matches the specified URL.
* @throws IOException if an error occurs.
*/
AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/ROArchiveEntryFile.java
================================================
package com.mucommander.commons.file;
import java.io.OutputStream;
/**
* Represents a file entry inside a read-only archive. Read-only archives are characterized by
* {@link AbstractArchiveFile#isWritable()} returning false.
*
* @see AbstractArchiveFile
* @see RWArchiveEntryFile
* @author Maxence Bernard
*/
public class ROArchiveEntryFile extends AbstractArchiveEntryFile {
protected ROArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {
super(url, archiveFile, entry);
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*
* @throws UnsupportedFileOperationException always
*/
@Override
@UnsupportedFileOperation
public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);
}
/**
* Always return {@link PermissionBits#EMPTY_PERMISSION_BITS}.
*/
@Override
public PermissionBits getChangeablePermissions() {
return PermissionBits.EMPTY_PERMISSION_BITS;
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*/
@Override
@UnsupportedFileOperation
public void delete() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.DELETE);
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*/
@Override
@UnsupportedFileOperation
public void mkdir() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*/
@Override
@UnsupportedFileOperation
public OutputStream getOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*/
@Override
@UnsupportedFileOperation
public void changePermissions(int permissions) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);
}
/**
* Always throws {@link UnsupportedFileOperationException} when called.
*/
@Override
@UnsupportedFileOperation
public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);
}
/**
* Always returns 0.
*/
@Override
public long getFreeSpace() {
return 0;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/RWArchiveEntryFile.java
================================================
package com.mucommander.commons.file;
import com.mucommander.commons.io.ByteCounter;
import com.mucommander.commons.io.CounterOutputStream;
import javax.swing.tree.DefaultMutableTreeNode;
import java.io.IOException;
import java.io.OutputStream;
/**
* Represents a file entry inside a read-write archive. Read-write archives are characterized by
* {@link AbstractArchiveFile#isWritable()} returning false.
*
* @see AbstractArchiveFile
* @see ROArchiveEntryFile
* @author Maxence Bernard
*/
public class RWArchiveEntryFile extends AbstractArchiveEntryFile {
RWArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {
super(url, archiveFile, entry);
}
/**
* Updates this entry's attributes in the archive and returns true if the update went OK.
*
* @return true if the attributes were successfully updated in the archive.
*/
private boolean updateEntryAttributes() {
try {
((AbstractRWArchiveFile)archiveFile).updateEntry(entry);
return true;
}
catch(IOException e) {
return false;
}
}
/**
* @throws IOException if the entry does not exist within the archive
*/
@Override
public void setLastModifiedDate(long lastModified) throws IOException {
if (!entry.exists()) {
throw new IOException();
}
long oldDate = entry.getLastModifiedDate();
entry.setDate(lastModified);
boolean success = updateEntryAttributes();
if (!success) {
// restore old date if attributes could not be updated
entry.setDate(oldDate);
throw new IOException();
}
}
/**
* Always returns {@link PermissionBits#FULL_PERMISSION_BITS}.
*/
@Override
public PermissionBits getChangeablePermissions() {
// Todo: some writable archive implementations may not have full 'set' permissions support, or even no notion of permissions
return PermissionBits.FULL_PERMISSION_BITS;
}
/**
* Deletes this entry from the associated AbstractArchiveFile.
* IOException in any of the following
* cases:
*
*
*
* @throws IOException in any of the cases listed above.
*/
@Override
public void delete() throws IOException {
if (!entry.exists()) {
throw new IOException();
}
AbstractRWArchiveFile rwArchiveFile = (AbstractRWArchiveFile)archiveFile;
// Throw an IOException if this entry is a non-empty directory
if (isDirectory()) {
ArchiveEntryTree tree = rwArchiveFile.getArchiveEntryTree();
if (tree != null) {
DefaultMutableTreeNode node = tree.findEntryNode(entry.getPath());
if (node != null && node.getChildCount() > 0) {
throw new IOException();
}
}
}
// Delete the entry in the archive file
rwArchiveFile.deleteEntry(entry);
// Non-existing entries are considered as zero-length regular files
entry.setDirectory(false);
entry.setSize(0);
entry.setExists(false);
}
/**
* Creates this entry as a directory in the associated AbstractArchiveFile.
* IOException if this entry
* already exists in the archive or if an I/O error occurred.
*
* @throws IOException if this entry already exists in the archive or if an I/O error occurred.
*/
@Override
public void mkdir() throws IOException {
if (entry.exists()) {
throw new IOException();
}
AbstractRWArchiveFile rwArchivefile = (AbstractRWArchiveFile)archiveFile;
// Update the ArchiveEntry
entry.setDirectory(true);
entry.setDate(System.currentTimeMillis());
entry.setSize(0);
// Add the entry to the archive file
rwArchivefile.addEntry(entry);
// The entry now exists
entry.setExists(true);
}
/**
* Returns an OutputStream that allows to write this entry's contents.
* SchemeHandler is an interface that allows {@link FileURL} to be specialized for a particular scheme.
* It provides a number of scheme-specific features:
*
*
* Handler registration
* FileURL registers a number of handlers for the schemes/protocols supported by the muCommander file API.
* Additional handlers can be registered dynamically using {@link FileURL#registerHandler(String, SchemeHandler)}.
* Likewise, existing handlers can be unregistered or replaced at runtime using
* {@link FileURL#registerHandler(String, SchemeHandler)} and {@link FileURL#unregisterHandler(String)}.
*
* FileURL uses a default handler for schemes that do not have a specific handler registered.
*
* @see DefaultSchemeHandler
* @see com.mucommander.commons.file.FileURL#registerHandler(String, SchemeHandler)
* @see SchemeParser
* @author Maxence Bernard
*/
public interface SchemeHandler {
/**
* Returns the SchemeParser that turns URL strings of a particular scheme into {@link FileURL} objects.
*
* @return the SchemeParser that turns URL strings of a particular scheme into {@link FileURL} objects
*/
SchemeParser getParser();
/**
* Returns the authentication realm of the given location, i.e. the base location throughout which a set of
* credentials can be used. Any property contained by the specified FileURL will be carried over in the returned
* FileURL. On the contrary, credentials will not be copied, the returned URL always has no credentials.
*
* null if the scheme doesn't have any.
* null if the scheme doesn't have any
*/
Credentials getGuestCredentials();
/**
* Returns the type of authentication used by the scheme's file protocol. The returned value is one of the constants
* defined in {@link AuthenticationType}.
*
* @return the type of authentication used by the scheme's file protocol
*/
AuthenticationType getAuthenticationType();
/**
* Returns the scheme's path separator, which serves as a delimiter for path fragments. For most schemes, this is
* the forward slash character.
*
* @return this scheme's path separator
*/
String getPathSeparator();
/**
* Returns the scheme's standard port, -1 if the scheme doesn't have any. Some file protocols may not
* have a notion of standard port or even no use for the port part at all, for example those that are not TCP
* or UDP based such as the local 'file' scheme.
*
* @return the scheme's standard port
*/
int getStandardPort();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/SchemeParser.java
================================================
package com.mucommander.commons.file;
import java.net.MalformedURLException;
/**
* SchemeParser is an interface that provides a single {@link #parse(String, FileURL)} method used by
* {@link FileURL#getFileURL(String)} to turn a URL string into a corresponding FileURL instance.
*
* @see FileURL#getFileURL(String)
* @see com.mucommander.commons.file.SchemeHandler
* @author Maxence Bernard
*/
public interface SchemeParser {
/**
* Extracts the different parts from the given URL string and sets them in the specified FileURL instance.
* The FileURL is empty when it is passed, with just the handler set. The scheme, host, port, login, password, path,
* ... parts must all be set, using the corresponding setter methods.
*
* padPermission static methods that allows to pad unsupported permission
* bits with default values.
*
* @author Maxence Bernard
*/
public class SimpleFilePermissions extends GroupedPermissionBits implements FilePermissions {
/** The permissions mask */
protected PermissionBits mask;
/**
* Creates a new SimpleFilePermissions using the specified UNIX-style permission int for permission values and
* {@link #FULL_PERMISSION_BITS full permissions mask}.
*
* @param permissions a UNIX-style permission int that holds permission values.
*/
public SimpleFilePermissions(int permissions) {
this(permissions, FULL_PERMISSION_BITS);
}
/**
* Creates a new SimpleFilePermissions using the specified UNIX-style permission int values for permission values
* and mask.
*
* @param permissions a UNIX-style permission int that holds permission values.
* @param mask a UNIX-style permission int which defines which permission bits are supported.
*/
public SimpleFilePermissions(int permissions, int mask) {
this(permissions, new GroupedPermissionBits(mask));
}
/**
* Creates a new SimpleFilePermissions using the specified UNIX-style permission int and permission mask.
*
* @param permissions a UNIX-style permission int that holds permission values.
* @param mask a permission mask which defines which permission bits are supported.
*/
public SimpleFilePermissions(int permissions, PermissionBits mask) {
super(permissions);
this.mask = mask;
}
/**
* Pads the given permissions with the specified ones: the permission bits that are not supported
* (as reported by the supplied permissions mask) are replaced by those of the default permissions.
* That means:
* - if the mask indicates that all permission bits are supported (mask = 777 octal), the supplied permissions will
* simply be returned, without using any of the default permissions
* - if the mask indicates that none of the permission bits are supported (mask = 0), the default permissions will
* be returned, without using any of the supplied permissions
*
* @param permissions the permissions to pad with default permissions for the bits that are not supported
* @param defaultPermissions permissions to use for the bits that are not supported
* @return the permissions padded with the default permissions
*/
public static FilePermissions padPermissions(FilePermissions permissions, FilePermissions defaultPermissions) {
int permissionMask = permissions.getMask().getIntValue();
return new SimpleFilePermissions((
permissions.getIntValue() & permissionMask) | (~permissionMask & defaultPermissions.getIntValue()),
defaultPermissions.getMask());
}
/**
* Pads the given permissions with the specified ones: the permission bits that are not supported
* (as reported by the supplied permissions mask) are replaced by those of the default permissions.
* That means:
* - if the mask indicates that all permission bits are supported (mask = 777 octal), the supplied permissions will
* simply be returned, without using any of the default permissions
* - if the mask indicates that none of the permission bits are supported (mask = 0), the default permissions will
* be returned, without using any of the supplied permissions
*
* @param permissions the permissions to pad with default permissions for the bits that are not supported
* @param supportedPermissionsMask the bit mask that indicates which bits of the given permissions are supported
* @param defaultPermissions permissions to use for the bits that are not supported
* @return the given permissions padded with the default permissions
*/
public static int padPermissions(int permissions, int supportedPermissionsMask, int defaultPermissions) {
return (permissions & supportedPermissionsMask) | (~supportedPermissionsMask & defaultPermissions);
}
@Override
public PermissionBits getMask() {
return mask;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/SingleArchiveEntryIterator.java
================================================
package com.mucommander.commons.file;
/**
* This class is an implementation of {@link ArchiveEntryIterator} that iterates through a single archive entry
* specified at creation time. The entry passed to the constructor may be null -- the iterator will
* act as an empty one. {@link #close()} is implemented as a no-op.
*
* @author Maxence Bernard
*/
public class SingleArchiveEntryIterator implements ArchiveEntryIterator {
/** The single entry to iterate through */
protected ArchiveEntry entry;
public SingleArchiveEntryIterator(ArchiveEntry entry) {
this.entry = entry;
}
@Override
public ArchiveEntry nextEntry() {
if (entry == null) {
return null;
}
ArchiveEntry nextEntry = entry;
entry = null;
return nextEntry;
}
/**
* Implemented as a no-op (nothing to close).
*/
@Override
public void close() {
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/SyncedFileAttributes.java
================================================
package com.mucommander.commons.file;
/**
* SyncedFileAttributes is a FileAttributes implementation which allows attribute values to be automatically
* updated when accessed after a certain amount of time (the 'time to live') since their last update. The update
* is performed by the abstract {@link #updateAttributes()} method and is triggered by any of the attribute getters.
* A typical usage for this class is for remote file systems that need to keep file attributes in sync with a server,
* {@link #updateAttributes()} can retrieve a fresh copy of the attributes on the server.
*
* true, attributes are automatically updated
*/
public SyncedFileAttributes(long ttl, boolean updateAttributesNow) {
setTtl(ttl); // also sets the expiration date
if (updateAttributesNow) {
checkForExpiration(true); // force attributes update
}
}
/**
* Returns the attributes' 'time to live', i.e. the amount of time since the last update after which attributes will
* be automatically updated when any of the getter method is called.
*
* @return the attributes' 'time to live', in milliseconds
*/
public long getTtl() {
return ttl;
}
/**
* Sets the attributes' 'time to live', i.e. the amount of time since the last update after which attributes will
* be automatically updated when any of the getter method is called.
* Note that setting the 'time to live' causes the expiration date to be updated with {@link #updateExpirationDate()}.
*
* @param ttl the attributes' 'time to live', in milliseconds
*/
public void setTtl(long ttl) {
this.ttl = ttl;
// update the expiration date
updateExpirationDate();
}
/**
* Returns the attributes' expiration timestamp/date, the date after which attributes values will be automatically
* updated when any of the getter method is called.
*
* @return the attributes' expiration timestamp/date
*/
public long getExpirationDate() {
return expirationDate;
}
/**
* Sets the attributes' expiration timestamp/date, the date after which attributes values will be automatically
* updated when they are accessed using any of the getter methods.
*
* @param expirationDate the attributes expiration timestamp/date
*/
public void setExpirationDate(long expirationDate) {
this.expirationDate = expirationDate;
}
/**
* Updates the attributes' expiration date to 'now' + 'ttl' (as returned by {@link #getTtl()}).
* This method is called after attributes have been automatically updated. It can also be called after attribute
* values have been manually updated using the setter methods.
*/
public void updateExpirationDate() {
setExpirationDate(ttl < 0 ? Long.MAX_VALUE : System.currentTimeMillis() + getTtl());
}
/**
* Returns true if attributes have expired, i.e. the {@link #getExpirationDate()} expiration date has
* passed, false if attributes are still 'fresh'. This method also returns false if
* automatic attributes' update has been disabled ('time to live' set to a negative value), or if attributes are
* currently being updated.
*
* @return true if attributes have expired
*/
public boolean hasExpired() {
return ttl>=0 // prevents automatic updates if ttl is set to a negative value
&& !isUpdating() // causes getters to return the current value while attributes are being updated
&& System.currentTimeMillis()>expirationDate;
}
/**
* Returns true if attributes are currently being updated.
*
* @return true if attributes are currently being updated
*/
private synchronized boolean isUpdating() {
return isUpdating;
}
/**
* Sets whether attributes are currently being updated.
*
* @param isUpdating true if attributes are currently being updated
*/
private synchronized void setUpdating(boolean isUpdating) {
this.isUpdating = isUpdating;
}
/**
* Checks if the attributes have expired and if they have, calls {@link #updateAttributes()} to refresh their
* values.
*
* @param forceUpdate if true, attributes will systematically be updated, without checking the expiration date
*/
protected void checkForExpiration(boolean forceUpdate) {
if (forceUpdate || hasExpired()) {
// After this method is called, hasExpired() returns false so that implementations of updateAttributes()
// can query attribute getters without entering a loop of death.
setUpdating(true);
// Updates attribute values
updateAttributes();
// Update expiration date after the attribute have actually been updated, note that it may take a while
// for remote file protocols to retrieve attributes.
updateExpirationDate();
// OK we're done
setUpdating(false);
}
}
////////////////////////
// Overridden methods //
////////////////////////
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getPath() {
checkForExpiration(false);
return super.getPath();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public boolean exists() {
checkForExpiration(false);
return super.exists();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public long getLastModifiedDate() {
checkForExpiration(false);
return super.getLastModifiedDate();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public long getSize() {
checkForExpiration(false);
return super.getSize();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public boolean isDirectory() {
checkForExpiration(false);
return super.isDirectory();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public FilePermissions getPermissions() {
checkForExpiration(false);
return super.getPermissions();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getOwner() {
checkForExpiration(false);
return super.getOwner();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getGroup() {
checkForExpiration(false);
return super.getGroup();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public short getReplication() {
checkForExpiration(false);
return super.getReplication();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public long getBlockSize() {
checkForExpiration(false);
return super.getBlockSize();
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Updates the attribute values. This method is automatically called when attributes are expired and one of the
* attribute getters is called. The implementation may choose to update only certain attributes, or skip updates
* under certain conditions.
*/
public abstract void updateAttributes();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/UnsupportedFileOperation.java
================================================
package com.mucommander.commons.file;
import java.lang.annotation.*;
/**
* @author Maxence Bernard
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UnsupportedFileOperation {
}
================================================
FILE: src/main/java/com/mucommander/commons/file/UnsupportedFileOperationException.java
================================================
package com.mucommander.commons.file;
import java.io.IOException;
/**
* This exception can be thrown by certain {@link AbstractFile} methods, when the corresponding operation
* is not available, either because the underlying file protocol does not support it, or because it is not
* implemented. This exception may also be thrown by file operations that depend on another file operation that is
* not supported.
*
* Unlike java.lang.UnsupportedOperationException, this exception is not a
* RuntimeException and must therefore be caught explicitly.
*
* AbstractFile method that throws this exception once must throw it
* always, for any file instance.
*
* @author Maxence Bernard
* @see UnsupportedFileOperation
* @see AbstractFile
*/
public class UnsupportedFileOperationException extends IOException {
/** The {@link FileOperation} this exception refers to */
private final FileOperation op;
/**
* Creates a new UnsupportedFileOperationException corresponding to the specified {@link FileOperation}.
*
* @param op the {@link FileOperation} this exception refers to.
*/
public UnsupportedFileOperationException(FileOperation op) {
super();
this.op = op;
}
/**
* Creates a new UnsupportedFileOperationException corresponding to the specified {@link FileOperation}
* with a custom message.
*
* @param op the {@link FileOperation} this exception refers to.
* @param message a message describing the exception cause.
*/
public UnsupportedFileOperationException(FileOperation op, String message) {
super(message);
this.op = op;
}
/**
* Returns the {@link FileOperation} this exception refers to.
*
* @return the {@link FileOperation} this exception refers to.
*/
public FileOperation getFileOperation() {
return op;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/WrapperArchiveEntryIterator.java
================================================
package com.mucommander.commons.file;
import java.io.IOException;
import java.util.Iterator;
/**
* This class wraps a java.util.Iterator and implements ArchiveEntryIterator by
* delegating methods to their java.util.Iterator equivalent. {@link #close()} is implemented as a no-op.
*
* @author Maxence Bernard
*/
public class WrapperArchiveEntryIterator implements ArchiveEntryIterator {
/** Wrapped iterator */
protected Iterator extends ArchiveEntry> iterator;
/**
* Creates a new WrapperArchiveEntryIterator that iterates through the given
* java.util.Iterator's elements.
*
* @param iterator the wrapped iterator
*/
public WrapperArchiveEntryIterator(Iterator extends ArchiveEntry> iterator) {
this.iterator = iterator;
}
@Override
public ArchiveEntry nextEntry() {
return iterator.hasNext() ? iterator.next() : null;
}
/**
* Implemented as a no-op (nothing to close).
*/
@Override
public void close() throws IOException {
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/archiver/ArchiveFormat.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2017 Oleg Trifonov
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see getArchiver methods can be used to retrieve an Archiver
* instance for a specified archive format. A list of available archive formats can be dynamically retrieved
* using {@link #getFormats(boolean) getFormats}.
*
*
*
*
* @author Maxence Bernard
*/
public abstract class Archiver {
/** The underlying stream this archiver is writing to */
protected OutputStream out;
/** Archive format of this Archiver */
protected ArchiveFormat format;
/** Support output stream for archiving files */
boolean supportStream;
/**
* Creates a new Archiver.
*
* @param out the OutputStream this Archiver will write to
*/
Archiver(OutputStream out) {
this.out = out;
this.supportStream = true;
}
/**
* Returns the OutputStream this Archiver is writing to.
*
* @return the OutputStream this Archiver is writing to
*/
public OutputStream getOutputStream() {
return out;
}
/**
* Returns the archiver format used by this Archiver. See format constants.
* @return archiver format code
*/
public ArchiveFormat getFormat() {
return this.format;
}
/**
* Sets the archiver format used by this Archiver, for internal use only.
*/
private void setFormat(ArchiveFormat format) {
this.format = format;
}
/**
* Checks if the format used by this Archiver can store an optional comment.
* @return true if the format used by this Archiver can store an optional comment.
*/
public boolean supportsComment() {
return formatSupportsComment(this.format);
}
/**
* @return true if the archiver supports writing with streams
*/
public boolean supportsStream() {
return supportStream;
}
/**
* Sets an optional comment in the archive, the {@link #supportsComment()} or
* {@link #formatSupportsComment(ArchiveFormat)} must first be called to make sure
* the archive format supports comment, otherwise calling this method will have no effect.
*
*
*
*
* @param entryPath
* @param isDirectory
*
* @return normalized path
*/
String normalizePath(String entryPath, boolean isDirectory) {
// Replace any \ character by /
entryPath = entryPath.replace('\\', '/');
// If entry is a directory, make sure the path contains a trailing /
if (isDirectory && !entryPath.endsWith("/"))
entryPath += "/";
return entryPath;
}
////////////////////
// Static methods //
////////////////////
/**
* Returns an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to.
* null is returned if the specified format is not valid.
*
* OutputStream. Note that if the file exists, its contents
* will be overwritten. Write bufferring is used under the hood to improve performance.
*
* @param file the AbstractFile which the returned Archiver will write entries to
* @param format an archive format
* @return an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to ;
* null if the specified format is not valid.
* @throws IOException if the file cannot be opened for write, or if an error occurred while intializing the archiver
* @throws UnsupportedFileOperationException if the underlying filesystem does not support write operations
*/
public static Archiver getArchiver(AbstractFile file, ArchiveFormat format) throws IOException, UnsupportedFileOperationException {
// switch(format) {
// case ISO:
// return new ISOArchiver(file);
// }
OutputStream out = null;
if (file.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE)) {
try {
// Important: if the file exists, it has to be overwritten as AbstractFile#getRandomAccessOutputStream()
// does NOT overwrite the file. This fixes bug #30.
if (file.exists()) {
file.delete();
}
out = new BufferedRandomOutputStream(file.getRandomAccessOutputStream());
} catch (IOException e) {
// Fall back to a regular OutputStream
}
}
if (out == null) {
out = new BufferedOutputStream(file.getOutputStream());
}
return getArchiver(out, format);
}
/**
* Returns an Archiver for the specified format and that uses the given OutputStream to write entries to.
* null is returned if the specified format is not valid. Whenever possible, a
* {@link RandomAccessOutputStream} should be supplied as some formats take advantage of having a random write access.
*
* @param out the OutputStream which the returned Archiver will write entries to
* @param format an archive format
* @return an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to ;
* null if the specified format is not valid.
* @throws IOException if an error occurred while initializing the archiver
*/
private static Archiver getArchiver(OutputStream out, ArchiveFormat format) throws IOException {
Archiver archiver;
switch (format) {
case ZIP:
archiver = new ZipArchiver(out);
break;
case GZ:
archiver = new SingleFileArchiver(new GZIPOutputStream(out));
break;
case BZ2:
archiver = new SingleFileArchiver(createBzip2OutputStream(out));
break;
case TAR:
archiver = new TarArchiver(out);
break;
case TAR_GZ:
archiver = new TarArchiver(new GZIPOutputStream(out));
break;
case TAR_BZ2:
archiver = new TarArchiver(createBzip2OutputStream(out));
break;
// case ISO:
// throw new IllegalStateException("ISO archiving not supported by stream");
default:
return null;
}
archiver.setFormat(format);
return archiver;
}
/**
* Creates and returns a Bzip2 OutputStream using the given OutputStream as the underlying
* stream.
*
* @param out the underlying stream
* @return a Bzip2 OutputStream
* @throws IOException if an error occurred while initializing the Bzip2 OutputStream
*/
private static OutputStream createBzip2OutputStream(OutputStream out) throws IOException {
// Writes the 2 magic bytes 'BZ', as required by CBZip2OutputStream. A quote from CBZip2OutputStream's Javadoc:
// "Attention: The caller is responsible to write the two BZip2 magic bytes "BZ" to the specified stream
// prior to calling this constructor."
out.write('B');
out.write('Z');
return new CBZip2OutputStream(out);
}
/**
* Returns an array of available archive formats, single entry formats or many entries formats
* depending on the value of the specified boolean parameter.
*
* @param manyEntries if true, a list of many entries formats (a subset of single entry formats) will be returned
* @return an array of available archive formats
*/
public static ArchiveFormat[] getFormats(boolean manyEntries) {
if (!manyEntries) {
return ArchiveFormat.values();
}
int cnt = 0;
for (ArchiveFormat af : ArchiveFormat.values()) {
if (af.supportManyEntries) {
cnt++;
}
}
ArchiveFormat[] result = new ArchiveFormat[cnt];
int i = 0;
for (ArchiveFormat af : ArchiveFormat.values()) {
if (af.supportManyEntries) {
result[i++] = af;
}
}
return result;
}
/**
* Returns true if the specified archive format can store an optional comment.
*
* @param format an archive format
* @return true if the specified archive format can store an optional comment
*/
public static boolean formatSupportsComment(ArchiveFormat format) {
return format == ArchiveFormat.ZIP;
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Creates a new entry in the archive using the given relative path and file attributes, and returns an
* OutputStream to write the entry's contents. The specified file attributes are used to determine
* whether the entry is a directory or a regular file, and to set the entry's size, permissions and date.
*
* null otherwise. The OutputStream must not be closed once
* it has been used (Archiver takes care of this), only the {@link #close() close} method has to be called when
* all entries have been created.
*
* OutputStream to write the entry's contents.
* @throws IOException if this Archiver failed to write the entry, or in the case of a single entry archiver, if
* this method was called more than once.
*/
public abstract OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException;
/**
* @return Name of current file being processed
*/
public String getProcessingFile() {
return null;
}
/**
* Written bytes in total without the current file progress
* @return number of bytes written as a long
*/
public long totalWrittenBytes() {
return -1;
}
/**
* Written bytes to the current file being processed, will be the same size as the
* file if complete.
* @return number of bytes written as a long
*/
public long writtenBytesCurrentFile() {
return -1;
}
/**
* @return Size of the current file being processed in bytes
*/
public long currentFileLength() {
return -1;
}
/**
* Finish the archiving process when all files have been added.
*/
public abstract void postProcess() throws IOException;
/**
* Closes the underlying OuputStream and ressources used by this Archiver to write the archive. This method
* must be called when all entries have been added to the archive.
*/
public abstract void close() throws IOException;
}
================================================
FILE: src/main/java/com/mucommander/commons/file/archiver/ISOArchiver.java
================================================
package com.mucommander.commons.file.archiver;
import com.github.stephenc.javaisotools.eltorito.impl.ElToritoConfig;
import com.github.stephenc.javaisotools.iso9660.ConfigException;
import com.github.stephenc.javaisotools.iso9660.ISO9660Directory;
import com.github.stephenc.javaisotools.iso9660.ISO9660File;
import com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;
import com.github.stephenc.javaisotools.iso9660.impl.ISO9660Config;
import com.github.stephenc.javaisotools.iso9660.impl.ISOImageFileHandler;
import com.github.stephenc.javaisotools.joliet.impl.JolietConfig;
import com.github.stephenc.javaisotools.rockridge.impl.RockRidgeConfig;
import com.github.stephenc.javaisotools.sabre.HandlerException;
import com.github.stephenc.javaisotools.sabre.StreamHandler;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileAttributes;
import com.mucommander.commons.file.impl.iso.MuCreateISO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Archiver implementation using the ISO9660 archive format.
*
* @author Jeppe Vennekilde
*/
public class ISOArchiver extends Archiver {
private StreamHandler streamHandler;
private final ISO9660Config config;
private final ISO9660RootDirectory root;
//Adds support for longer file names & wider range of characters
private final boolean enableJoliet = true;
//Adds support for deeper directory hierarchies and even bigger file names (up to 255 bytes)
private final boolean enableRockRidge = true;
//Adds support for creation of bootable iso files (not implemented)
private final boolean enableElTorito = false;
private MuCreateISO createISOProcess = null;
ISOArchiver(AbstractFile file) {
super(null);
supportStream = false;
config = new ISO9660Config();
try {
config.allowASCII(false);
config.setInterchangeLevel(1);
//The rock ridge extension of ISO9660 allow directory depth to exceed 8
config.restrictDirDepthTo8(!enableRockRidge);
config.setPublisher(System.getProperty("user.name"));
//Max length of volume is 32 chars
config.setVolumeID(file.getName().substring(0, Math.min(file.getName().length(), 31)));
config.setDataPreparer(System.getProperty("user.name"));
config.forceDotDelimiter(true);
} catch (ConfigException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
root = new ISO9660RootDirectory();
try {
streamHandler = new ISOImageFileHandler(new File(file.getPath()));
} catch (FileNotFoundException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public OutputStream createEntry(String entryPath, FileAttributes attributes) {
try {
if (attributes.isDirectory()) {
String[] split = entryPath.split("\\\\");
ISO9660Directory dir = new ISO9660Directory(split[split.length-1]);
ISO9660Directory parent = getParentDirectory(entryPath);
if (parent != null) {
parent.addDirectory(dir);
}
} else {
try {
ISO9660File file = new ISO9660File(new File(attributes.getPath()));
ISO9660Directory parent = getParentDirectory(entryPath);
if (parent != null) {
parent.addFile(file);
}
} catch (HandlerException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
}
return null; // TODO !!!! NPE here !!!
}
/**
* Get the ISO9660Directory parent object the path belongs to
*
* @param isoPath the sub directory/file of the parent directory it will return
* @return an ISO9660Directory that is the parent of the provided path
*/
private ISO9660Directory getParentDirectory(String isoPath){
String[] directories = isoPath.split("\\\\");
//Initial directory (root)
ISO9660Directory parent = root;
for (int i = 0; i < directories.length - 1; i++){
ISO9660Directory dir = containsDirectory(parent,directories[i]);
if (dir == null) {
return null;
}
parent = dir;
}
return parent;
}
/**
* Check if an ISO9660Directory contain a provided sub directory
*
* @param parentDirectory the directory that will be searched
* @param isoSubDirPath the ISO path that will be used for reference to see
* if the parent directory contains the sub directory
* @return an ISO9660Directory that is sub directory of the parent directory
* null if it does not contain the sub directory
*/
private ISO9660Directory containsDirectory(ISO9660Directory parentDirectory, String isoSubDirPath){
for (ISO9660Directory directory : parentDirectory.getDirectories()) {
if (directory.getName().equals(isoSubDirPath)){
return directory;
}
}
return null;
}
@Override
public String getProcessingFile() {
return createISOProcess != null ? createISOProcess.getProcessingFile() : null;
}
@Override
public long totalWrittenBytes(){
return createISOProcess != null ? createISOProcess.totalWrittenBytes(): 0;
}
@Override
public long writtenBytesCurrentFile(){
return createISOProcess != null ? createISOProcess.writtenBytesCurrentFile(): 0;
}
@Override
public long currentFileLength(){
return createISOProcess != null ? createISOProcess.currentFileLength(): 0;
}
@Override
public void postProcess() throws IOException {
if (root.hasSubDirs() || !root.getFiles().isEmpty()) {
createISOProcess = new MuCreateISO(streamHandler, root);
RockRidgeConfig rrConfig = null;
if (enableRockRidge) {
// Rock Ridge support
rrConfig = new RockRidgeConfig();
rrConfig.setMkisofsCompatibility(false);
rrConfig.hideMovedDirectoriesStore(true);
rrConfig.forcePortableFilenameCharacterSet(true);
}
JolietConfig jolietConfig = null;
if (enableJoliet) {
// Joliet support
jolietConfig = new JolietConfig();
try {
if (config.getPublisher() instanceof String){
jolietConfig.setPublisher((String) config.getPublisher());
} else {
try {
jolietConfig.setPublisher((File) config.getPublisher());
} catch (HandlerException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
//Max volume id is 16 in the joliet config
jolietConfig.setVolumeID(config.getVolumeID().substring(0,Math.min(config.getVolumeID().length(), 15)));
if (config.getDataPreparer() != null){
if(config.getDataPreparer() instanceof String){
jolietConfig.setDataPreparer((String) config.getDataPreparer());
} else {
try {
jolietConfig.setDataPreparer((File) config.getDataPreparer());
} catch (Exception ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
jolietConfig.forceDotDelimiter(true);
} catch (ConfigException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
//ELTorito adds support for bootable ISO files, which is not supported at this time
//As this is for archiving, not creation of bootable ISO files (yet)
ElToritoConfig elToritoConfig = null;
try {
createISOProcess.process(config, rrConfig, jolietConfig, elToritoConfig);
} catch (HandlerException ex) {
Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
@Override
public void close() throws IOException {
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/archiver/SingleFileArchiver.java
================================================
package com.mucommander.commons.file.archiver;
import com.mucommander.commons.file.FileAttributes;
import java.io.IOException;
import java.io.OutputStream;
/**
* Generic single file Archiver.
*
* @author Maxence Bernard
*/
class SingleFileArchiver extends Archiver {
private boolean firstEntry = true;
SingleFileArchiver(OutputStream outputStream) {
super(outputStream);
}
/**
* This method is a no-op, and does nothing but throw an IOException if it is called more than once,
* which should never be the case as this Archiver is only meant to store one file.
*/
@Override
public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {
if (firstEntry) {
firstEntry = false;
} else {
throw new IOException();
}
return out;
}
@Override
public void close() throws IOException {
out.close();
}
@Override
public void postProcess() {}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/archiver/TarArchiver.java
================================================
package com.mucommander.commons.file.archiver;
import com.mucommander.commons.file.FileAttributes;
import com.mucommander.commons.file.FilePermissions;
import com.mucommander.commons.file.SimpleFilePermissions;
import com.mucommander.commons.file.impl.tar.provider.TarEntry;
import com.mucommander.commons.file.impl.tar.provider.TarOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Archiver implementation using the Tar archive format.
*
* @author Maxence Bernard
*/
class TarArchiver extends Archiver {
private final TarOutputStream tos;
private boolean firstEntry = true;
TarArchiver(OutputStream outputStream) {
super(outputStream);
this.tos = new TarOutputStream(outputStream);
// Specifies how to handle files which filename is > 100 chars (default is to fail!)
this.tos.setLongFileMode(TarOutputStream.LONGFILE_GNU);
}
@Override
public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {
// Start by closing current entry
if (!firstEntry) {
tos.closeEntry();
}
boolean isDirectory = attributes.isDirectory();
// create the entry
TarEntry entry = new TarEntry(normalizePath(entryPath, isDirectory));
// Use provided file's size (required by TarOutputStream) and date
long size = attributes.getSize();
if (!isDirectory && size >= 0) // Do not set size if file is directory or file size is unknown!
entry.setSize(size);
// Set the entry's date and permissions
entry.setModTime(attributes.getLastModifiedDate());
entry.setMode(SimpleFilePermissions.padPermissions(attributes.getPermissions(), isDirectory
? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS
: FilePermissions.DEFAULT_FILE_PERMISSIONS).getIntValue());
// Add the entry
tos.putNextEntry(entry);
if (firstEntry) {
firstEntry = false;
}
// Return the OutputStream that allows to write to the entry, only if it isn't a directory
return isDirectory ? null : tos;
}
@Override
public void close() throws IOException {
// Close current entry
if (!firstEntry) {
tos.closeEntry();
}
tos.close();
}
@Override
public void postProcess() {}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/archiver/ZipArchiver.java
================================================
package com.mucommander.commons.file.archiver;
import com.mucommander.commons.file.FileAttributes;
import com.mucommander.commons.file.FilePermissions;
import com.mucommander.commons.file.SimpleFilePermissions;
import com.mucommander.commons.file.impl.zip.provider.ZipEntry;
import com.mucommander.commons.file.impl.zip.provider.ZipOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Archiver implementation using the Zip archive format.
*
* @author Maxence Bernard
*/
class ZipArchiver extends Archiver {
private final ZipOutputStream zos;
private boolean firstEntry = true;
ZipArchiver(OutputStream outputStream) {
super(outputStream);
this.zos = new ZipOutputStream(outputStream);
}
/**
* Overrides Archiver's no-op setComment method as Zip supports archive comment.
*/
@Override
public void setComment(String comment) {
zos.setComment(comment);
}
@Override
public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {
// Start by closing current entry
if (!firstEntry) {
zos.closeEntry();
}
boolean isDirectory = attributes.isDirectory();
// Create the entry and use the provided file's date
ZipEntry entry = new ZipEntry(normalizePath(entryPath, isDirectory));
// Use provided file's size and date
long size = attributes.getSize();
if (!isDirectory && size >= 0) { // Do not set size if file is directory or file size is unknown!
entry.setSize(size);
}
entry.setTime(attributes.getLastModifiedDate());
entry.setUnixMode(SimpleFilePermissions.padPermissions(attributes.getPermissions(), isDirectory
? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS
: FilePermissions.DEFAULT_FILE_PERMISSIONS).getIntValue());
// Add the entry
zos.putNextEntry(entry);
if (firstEntry) {
firstEntry = false;
}
// Return the OutputStream that allows to write to the entry, only if it isn't a directory
return isDirectory ? null : zos;
}
@Override
public void close() throws IOException {
zos.close();
}
@Override
public void postProcess() {}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/compat/CompatURLConnection.java
================================================
package com.mucommander.commons.file.compat;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
/**
* @author Maxence Bernard
*/
class CompatURLConnection extends URLConnection {
protected AbstractFile file;
public CompatURLConnection(URL url) {
super(url);
// Not connected yet
}
public CompatURLConnection(URL url, AbstractFile file) {
super(url);
if(file!=null) {
this.file = file;
connected = true;
}
}
/**
* Checks if this URLConnection is connected and if it isn't, calls {@link #connect()} to connect it.
*
* @throws IOException if an error occurred while connecting this URLConnection
*/
private void checkConnected() throws IOException {
if(!connected)
connect();
}
/**
* Creates the {@link AbstractFile} instance corresponding to the URL location, only if no AbstractFile
* has been specified when this CompatURLConnection was created.
*
* @throws IOException if an error occurred while instanciating the AbstractFile
*/
@Override
public void connect() throws IOException {
if (!connected) {
file = FileFactory.getFile(url.toString(), true);
connected = true;
}
}
@Override
public InputStream getInputStream() throws IOException {
checkConnected();
return file.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
checkConnected();
return file.getOutputStream();
}
@Override
public long getLastModified() {
try {
checkConnected();
return file.getLastModifiedDate();
} catch(IOException e) {
return 0;
}
}
@Override
public long getDate() {
return getLastModified();
}
@Override
public int getContentLength() {
try {
checkConnected();
return (int)file.getSize();
} catch(IOException e) {
return -1;
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/compat/CompatURLStreamHandler.java
================================================
package com.mucommander.commons.file.compat;
import com.mucommander.commons.file.AbstractFile;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* @author Maxence Bernard
*/
public class CompatURLStreamHandler extends URLStreamHandler {
protected AbstractFile file;
public CompatURLStreamHandler() {
}
public CompatURLStreamHandler(AbstractFile file) {
this.file = file;
}
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new CompatURLConnection(url, file); // Note: file may be null
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/connection/ConnectionHandler.java
================================================
package com.mucommander.commons.file.connection;
import com.mucommander.commons.file.AuthException;
import com.mucommander.commons.file.Credentials;
import com.mucommander.commons.file.FileURL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* ConnectionHandler is a an abstract class that provides the basic operations for to interact with a server: establish
* the connection, keep it alive and close it.
*
* @see com.mucommander.commons.file.connection.ConnectionPool
* @author Maxence Bernard
*/
public abstract class ConnectionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionHandler.class);
/** URL of the server this ConnectionHandler connects to */
protected FileURL realm;
/** Credentials that are used to connect to the server */
protected Credentials credentials;
/** True if this ConnectionHandler is currently locked */
protected boolean isLocked;
/** Time at which the connection managed by this ConnectionHandler was last used */
private long lastActivityTimestamp;
/** Time at which the connection managed by this ConnectionHandler was last kept alive */
private long lastKeepAliveTimestamp;
/** Number of seconds of inactivity after which this ConnectionHandler's connection will be closed by ConnectionPool */
private long closeOnInactivityPeriod = DEFAULT_CLOSE_ON_INACTIVITY_PERIOD;
/** Number of seconds of inactivity after which this ConnectionHandler's connection will be kept alive by ConnectionPool */
private long keepAlivePeriod = DEFAULT_KEEP_ALIVE_PERIOD;
/** Default 'close on inactivity' period */
private final static long DEFAULT_CLOSE_ON_INACTIVITY_PERIOD = 300;
/** Default keep alive period (-1, keep alive disabled) */
private final static long DEFAULT_KEEP_ALIVE_PERIOD = -1;
/**
* Creates a new ConnectionHandler for the given server URL using the Credentials included in the URL (potentially
* null).
*
* @param serverURL URL of the server to connect to
*/
public ConnectionHandler(FileURL serverURL) {
realm = serverURL.getRealm();
this.credentials = serverURL.getCredentials();
}
/**
* Returns the URL of the server this ConnectionHandler connects to.
*
* @return the URL of the server this ConnectionHandler connects to
*/
public FileURL getRealm() {
return realm;
}
/**
* Returns the Credentials that are used to connect to the server, null if no credentials are used.
*
* @return the Credentials that are used to connect to the server, null if no credentials are used
*/
public Credentials getCredentials() {
return credentials;
}
/**
* Checks if the connection is currently active (as returned by {@link #isConnected()} and if it isn't, starts it
* by calling {@link #startConnection()}. Returns true if the connection was properly started, false if the
* connection was already active, or throws an IOException if the connection could not be started.
*
* @return Returns true if the connection was properly started, false if the connection was already active
* @throws IOException if the connection could not be started
*/
public boolean checkConnection() throws IOException {
if (!isConnected()) {
LOGGER.info("not connected, starting connection, this="+this);
startConnection();
return true;
}
return false;
}
/**
* Tries to lock this ConnectionHandler and returns true if it could be locked, false if it is already locked.
*
* @return true if it could be locked, false if it is already locked.
*/
synchronized boolean acquireLock() {
if (isLocked) {
LOGGER.info("!!!!! acquireLock() returning false, should not happen !!!!!", new Throwable());
return false;
}
isLocked = true;
return true;
}
/**
* Tries to release the lock on this ConnectionHandler and returns true if it could be locked, false if it
* is not locked.
*
* @return true if it could be locked, false if it is not locked
*/
public boolean releaseLock() {
synchronized(this) {
if (!isLocked) {
LOGGER.info("!!!!! releaseLock() returning false, should not happen !!!!!", new Throwable());
return false;
}
isLocked = false;
}
ConnectionPool.notifyConnectionHandlerLockReleased();
return true;
}
/**
* Returns true if this ConnectionHandler is currently locked.
*
* @return true if this ConnectionHandler is currently locked
*/
public synchronized boolean isLocked() {
return isLocked;
}
/**
* Updates the time at which the connection managed by this ConnectionHandler was last used to now (current time).
*/
void updateLastActivityTimestamp() {
lastActivityTimestamp = System.currentTimeMillis();
}
/**
* Returns the time at which the connection managed by this ConnectionHandler was last used.
*
* @return the time at which the connection managed by this ConnectionHandler was last used
*/
long getLastActivityTimestamp() {
return lastActivityTimestamp;
}
/**
* Updates the time at which the connection managed by this ConnectionHandler was last kept alive to now (current time).
*/
void updateLastKeepAliveTimestamp() {
lastKeepAliveTimestamp = System.currentTimeMillis();
}
/**
* Returns the time at which the connection managed by this ConnectionHandler was last kept alive.
*
* @return the time at which the connection managed by this ConnectionHandler was last kept alive
*/
long getLastKeepAliveTimestamp() {
return lastKeepAliveTimestamp;
}
/**
* Returns the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by
* calling {@link #closeConnection()}, -1 to indicate that the connection should not be automatically
* closed.
*
* -1 to indicate that the connection should not be automatically closed
*/
long getCloseOnInactivityPeriod() {
return closeOnInactivityPeriod;
}
/**
* Sets the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by calling
* {@link #closeConnection()}, -1 to prevent the connection from being automatically closed.
*
* -1 to indicate that the connection should not be automatically closed
*/
public void setCloseOnInactivityPeriod(long nbSeconds) {
closeOnInactivityPeriod = nbSeconds;
}
/**
* Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive
* by calling {@link #keepAlive()}, -1 to indicate that this connection should not be kept alive.
*
* -1 to indicate that this connection should not be kept alive
*/
long getKeepAlivePeriod() {
return keepAlivePeriod;
}
/**
* Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive
* by calling {@link #keepAlive()}, -1 to indicate that this connection should not be kept alive.
*
* -1 to indicate that this connection should not be kept alive
*/
protected void setKeepAlivePeriod(long nbSeconds) {
keepAlivePeriod = nbSeconds;
}
/**
* Returns true if the given Object is a ConnectionHandler whose realm and credentials are equal to
* those of this ConnectionHandler. The credentials comparison is password-sensitive.
*
* @param o the Object to compare for equality
* @see Credentials#equals(Object, boolean)
*/
public boolean equals(Object o) {
if (!(o instanceof ConnectionHandler)) {
return false;
}
ConnectionHandler connHandler = (ConnectionHandler)o;
return equals(connHandler.realm, connHandler.credentials);
}
/**
* Returns true if both the given realm and credentials are equal to those of this ConnectionHandler.
* The credentials comparison is password-sensitive.
*
* @param realm the FileURL to compare against this ConnectionHandler's
* @param credentials the Credentials to compare against this ConnectionHandler's
* @return true if both the given realm and credentials are equal to those of this ConnectionHandler
* @see Credentials#equals(Object, boolean)
*/
public boolean equals(FileURL realm, Credentials credentials) {
if (!this.realm.equals(realm, false, true)) {
return false;
}
// Compare credentials. One or both Credentials instances may be null.
// Note: Credentials.equals() considers null as equal to empty Credentials (see Credentials#isEmpty())
return (this.credentials == null && credentials == null)
|| (this.credentials != null && this.credentials.equals(credentials, true))
|| (credentials != null && credentials.equals(this.credentials, true));
}
/**
* Throws an {@link AuthException} using this connection handler's realm, credentials and the message passed as
* an argument (can be null). The FileURL instance representing the realm that is used to create
* the AuthException is a clone of this realm, making it safe for modification.
*
* @param message the message to pass to AuthException's constructor, can be null
* @throws AuthException always throws the created AuthException
*/
protected void throwAuthException(String message) throws AuthException {
FileURL clonedRealm = (FileURL)realm.clone();
clonedRealm.setCredentials(credentials);
throw new AuthException(clonedRealm, message);
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Starts the connection managed by this ConnectionHandler, and throws an IOException if the connection could not
* be established. This method may be called several times during the life of this ConnectionHandler, if the
* connection dropped and must be re-established.
*
* @throws IOException if an error occurred while trying to establish the connection
* @throws AuthException if an authentication error occurred (incorrect login or password, insufficient privileges...)
*/
public abstract void startConnection() throws IOException, AuthException;
/**
* Returns true if the connection managed by this ConnectionHandler is currently active/established,
* in a state that makes it possible to serve client requests.
*
* true if the connection managed by this ConnectionHandler is currently active/established
*/
public abstract boolean isConnected();
/**
* Closes the connection managed by this ConnectionHandler.
*
* ConnectionHandler that a
* ConnectionHandler has been released.
*/
static void notifyConnectionHandlerLockReleased() {
synchronized (connectionHandlers) {
// Notify any thread waiting for a ConnectionHandler to be released
connectionHandlers.notify();
}
}
/**
* Monitors connections and periodically:
*
*
*/
public void run() {
while (monitorThread != null) { // Thread will be interrupted by CloseConnectionThread if there are no more ConnectionHandler
long now = System.currentTimeMillis();
synchronized(connectionHandlers) { // Ensures that getConnectionHandler is not currently changing the list while we access it
for (IteratorAbstractContainsFilter using the specified generator and string, and operating in the
* specified mode.
*
* @param generator generates criterion values for files as requested
* @param s the string to compare criterion values against
* @param caseSensitive if true, this filter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractContainsFilter(CriterionValueGeneratorAbstractCriterionFilter implements the bulk of the {@link CriterionFilter} interface, matching
* files based on the criteria values generated by a given {@link CriterionValueGenerator}. The only method left for
* subclasses to implement is {@link #accept(Object)}.
*
* @author Maxence Bernard
*/
public abstract class AbstractCriterionFilterAbstractCriterionFilter using the specified {@link CriterionValueGenerator} and operating
* in non-inverted mode.
*
* @param generator generates criterion values for files as requested
*/
public AbstractCriterionFilter(CriterionValueGeneratorAbstractCriterionFilter using the specified {@link CriterionValueGenerator} and operating
* in the specified mode.
*
* @param generator generates criterion values for files as requested
* @param inverted if true, this filter will operate in inverted mode.
*/
AbstractCriterionFilter(CriterionValueGeneratortrue if this filter matched the given value, according to the current {@link #isInverted()}
* mode:
*
*
*
* @param value the value to test
* @return true if this filter matched the given value, according to the current inverted mode
*/
public boolean match(C value) {
return inverted ? reject(value) : accept(value);
}
/**
* Returns true if the given value was rejected by this filter, false if it was accepted.
*
* AbstractFile instances.
*
* @param values values to be tested
* @return an array of accepted AbstractFile instances
*/
public C[] filter(C[] values) {
Listtrue if all the values in the specified array were matched by
* {@link #match(Object)}, false if one of the values wasn't.
*
* @param values the values to be tested
* @return true if all the values in the specified array were accepted
*/
public boolean match(C[] values) {
for (C value : values) {
if (!match(value)) {
return false;
}
}
return true;
}
/**
* Convenience method that returns true if all the values in the specified array were accepted by
* {@link #accept(Object)}, false if one of the values wasn't.
*
* @param values the values to be tested
* @return true if all the values in the specified array were accepted
*/
public boolean accept(C[] values) {
for (C value : values) {
if (!accept(value)) {
return false;
}
}
return true;
}
/**
* Convenience method that returns true if all the values in the specified array were rejected by
* {@link #reject(Object)}, false if one of the values wasn't.
*
* @param values the values to be tested
* @return true if all the values in the specified array were rejected
*/
public boolean reject(C[] values) {
for (C value : values) {
if (!reject(value)) {
return false;
}
}
return true;
}
@Override
public boolean accept(AbstractFile file) {
return accept(generator.getCriterionValue(file));
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/AbstractEndsWithFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractEndsWithFilter using the specified generator and string, and operating in the
* specified mode.
*
* @param generator generates criterion values for files as requested
* @param s the string to compare criterion values against
* @param caseSensitive if true, this filter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractEndsWithFilter(CriterionValueGeneratorAbstractEndsWithFilter using the specified generator and string, and operating in the
* specified mode.
*
* @param generator generates criterion values for files as requested
* @param s the string to compare criterion values against
* @param caseSensitive if true, this filter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractEqualsFilter(CriterionValueGeneratorAbstractExtensionFilter using the specified generator and string, and operating in the
* specified mode.
*
* @param generator generates criterion values for files as requested
* @param extensions the extensions to compare criterion values against
* @param caseSensitive if true, this filter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
AbstractExtensionFilter(CriterionValueGeneratorAbstractFileFilter implements the bulk of the {@link FileFilter} interface. The only method left for
* subclasses to implement is {@link #accept(AbstractFile)}.
*
* @see AbstractFilenameFilter
* @author Maxence Bernard
*/
public abstract class AbstractFileFilter implements FileFilter {
/** True if this filter should operate in inverted mode and invert matches */
protected boolean inverted;
/**
* Creates a new AbstractFileFilter operating in non-inverted mode.
*/
public AbstractFileFilter() {
this(false);
}
/**
* Creates a new AbstractFileFilter operating in the specified mode.
*
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractFileFilter(boolean inverted) {
setInverted(inverted);
}
///////////////////////////////
// FileFilter implementation //
///////////////////////////////
public boolean isInverted() {
return inverted;
}
public void setInverted(boolean inverted) {
this.inverted = inverted;
}
public boolean match(AbstractFile file) {
return inverted ? reject(file) : accept(file);
}
public boolean reject(AbstractFile file) {
return !accept(file);
}
public AbstractFile[] filter(AbstractFile[] files) {
ListAbstractFilenameFilter implements the bulk of the {@link FilenameFilter} interface. The only method left
* for subclasses to implement is {@link #accept(Object)}.
*
* @author Maxence Bernard
*/
public abstract class AbstractFilenameFilter extends AbstractStringCriterionFilter implements FilenameFilter {
/**
* Creates a new case-insensitive AbstractFilenameFilter operating in non-inverted mode.
*/
public AbstractFilenameFilter() {
this(false, false);
}
/**
* Creates a new AbstractFilenameFilter operating in non-inverted mode.
*
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public AbstractFilenameFilter(boolean caseSensitive) {
this(caseSensitive, false);
}
/**
* Creates a new AbstractFilenameFilter operating in the specified mode.
*
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractFilenameFilter(boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/AbstractPathFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractPathFilter implements the bulk of the {@link PathFilter} interface. The only method left
* for subclasses to implement is {@link #accept(Object)}.
*
* @author Maxence Bernard
*/
public abstract class AbstractPathFilter extends AbstractStringCriterionFilter implements PathFilter {
/**
* Creates a new case-insensitive AbstractPathFilter operating in non-inverted mode.
*/
public AbstractPathFilter() {
this(false, false);
}
/**
* Creates a new AbstractPathFilter operating in non-inverted mode.
*
* @param caseSensitive if true, this FilePathFilter will be case-sensitive
*/
public AbstractPathFilter(boolean caseSensitive) {
this(caseSensitive, false);
}
/**
* Creates a new AbstractPathFilter operating in the specified mode.
*
* @param caseSensitive if true, this FilePathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractPathFilter(boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/AbstractRegexpFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractRegexpFilter matching the specified regexp and operating in the specified
* modes.
*
* @param generator generates criterion values for files as requested
* @param regexp regular expression that matches string values.
* @param caseSensitive whether the regular expression is case sensitive or not.
* @param inverted if true, this filter will operate in inverted mode.
* @throws PatternSyntaxException if the syntax of the regular expression is not correct.
*/
public AbstractRegexpFilter(CriterionValueGeneratortrue if the specified value matches the filter's regular expression.
*
* @param value value to match against the filter's regular expression.
* @return true if the specified value matches the filter's regular expression,
* false otherwise.
*/
@Override
public boolean accept(String value) {
return pattern.matcher(value).matches();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/AbstractStartsWithFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AbstractStartsWithFilter using the specified generator and string, and operating in the
* specified mode.
*
* @param generator generates criterion values for files as requested
* @param s the string to compare criterion values against
* @param caseSensitive if true, this filter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractStartsWithFilter(CriterionValueGeneratorAbstractCriterionFilter implements the bulk of the {@link StringCriterionFilter} interface, matching
* files based on the criteria values generated by a given {@link CriterionValueGenerator}. The only method left for
* subclasses to implement is {@link #accept(Object)}.
*
* @see AbstractPathFilter
* @see AbstractFilenameFilter
* @author Maxence Bernard
*/
public abstract class AbstractStringCriterionFilter extends AbstractCriterionFilterAbstractStringCriterionFilter operating in non-inverted mode.
*
* @param generator generates criterion values for files as requested
*/
public AbstractStringCriterionFilter(CriterionValueGeneratorAbstractStringCriterionFilter operating in non-inverted mode.
*
* @param generator generates criterion values for files as requested
* @param caseSensitive if true, this FilePathFilter will be case-sensitive
*/
public AbstractStringCriterionFilter(CriterionValueGeneratorAbstractStringCriterionFilter that operates in the specified mode.
*
* @param generator generates criterion values for files as requested
* @param caseSensitive if true, this FilePathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public AbstractStringCriterionFilter(CriterionValueGeneratorAndFileFilter operating in non-inverted mode and containing the specified filters,
* if any.
*
* @param filters filters to add to this chained filter.
*/
public AndFileFilter(FileFilter... filters) {
this(false, filters);
}
/**
* Creates a new AndFileFilter operating in the specified mode and containing the specified filters,
* if any.
*
* @param inverted if true, this filter will operate in inverted mode.
* @param filters filters to add to this chained filter.
*/
public AndFileFilter(boolean inverted, FileFilter... filters) {
super(inverted, filters);
}
/**
* Calls {@link #match(com.mucommander.commons.file.AbstractFile)} on each of the registered filters, and returns
* true if all of them matched the given file, false if one of them didn't.
*
* true.
*
* @param file the file to test against the registered filters
* @return if the file was matched by all filters, false if one of them didn't
*/
@Override
public boolean accept(AbstractFile file) {
for (FileFilter filter : filters) {
if (!filter.match(file)) {
return false;
}
}
return true;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/AttributeFileFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see AttributeFileFilter matches files which have a specific attribute set.
* Here's a list of supported file attributes:
*
*
*
* AttributeFileFilter matching files that have the specified attribute set and operating
* in non-inverted mode.
*
* @param attribute the attribute to test files against
*/
public AttributeFileFilter(FileAttribute attribute) {
this(attribute, false);
}
/**
* Creates a new AttributeFileFilter matching files that have the specified attribute set and operating
* in the specified mode.
*
* @param attribute the attribute to test files against
* @param inverted if true, this filter will operate in inverted mode.
*/
public AttributeFileFilter(FileAttribute attribute, boolean inverted) {
super(inverted);
this.attribute = attribute;
}
/**
* Returns the attribute which files are tested against.
*
* @return the attribute which files are tested against.
*/
public FileAttribute getAttribute() {
return attribute;
}
/**
* Sets the attribute which files are tested against.
*
* @param attribute the attribute which files are tested against.
*/
public void setAttribute(FileAttribute attribute) {
this.attribute = attribute;
}
@Override
public boolean accept(AbstractFile file) {
switch(attribute) {
case DIRECTORY:
return file.isDirectory();
case FILE:
return !file.isDirectory();
case BROWSABLE:
return file.isBrowsable();
case ARCHIVE:
return file.isArchive();
case SYMLINK:
return file.isSymlink();
case HIDDEN:
return file.isHidden();
case ROOT:
return file.isRoot();
case SYSTEM:
return file.isSystem();
default:
return true;
}
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/ChainedFileFilter.java
================================================
package com.mucommander.commons.file.filter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* ChainedFileFilter combines one or several {@link FileFilter} to act as just one.
*{@link #addFileFilter(FileFilter)} and {@link #removeFileFilter(FileFilter)} allow to add or remove a
* FileFilter, {@link #getFileFilterIterator()} to iterate through all the registered filters.
*
* ChainedFileFilter operating in non-inverted mode and containing the specified filters,
* if any.
*
* @param filters filters to add to this chained filter.
*/
public ChainedFileFilter(FileFilter... filters) {
this(false, filters);
}
/**
* Creates a new ChainedFileFilter operating in the specified mode and containing the specified filters,
* if any.
*
* @param inverted if true, this filter will operate in inverted mode.
* @param filters filters to add to this chained filter.
*/
public ChainedFileFilter(boolean inverted, FileFilter... filters) {
super(inverted);
for (FileFilter filter : filters)
addFileFilter(filter);
}
/**
* Adds a new {@link FileFilter} to the list of chained filters.
*
* @param filter the FileFilter to add
*/
public void addFileFilter(FileFilter filter) {
filters.add(filter);
}
/**
* Removes a {@link FileFilter} from the list of chained filters. Does nothing if the given FileFilter
* is not contained by this ChainedFileFilter.
*
* @param filter the FileFilter to remove
*/
public void removeFileFilter(FileFilter filter) {
filters.remove(filter);
}
/**
* Returns an Iterator that traverses all the registered filters.
*
* @return an Iterator that traverses all the registered filters.
*/
public Iteratortrue if this chained filter doesn't contain any file filter.
*
* @return true if this chained filter doesn't contain any file filter.
*/
public boolean isEmpty() {
return filters.isEmpty();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/ContainsFilenameFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link FilenameFilter} matches filenames that contain a specified string that can be located anywhere in the
* filename.
*
* @author Maxence Bernard
*/
public class ContainsFilenameFilter extends AbstractContainsFilter implements FilenameFilter {
/**
* Creates a new case-insensitive ContainsFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
*/
public ContainsFilenameFilter(String s) {
this(s, false, false);
}
/**
* Creates a new ContainsFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public ContainsFilenameFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new ContainsFilenameFilter operating in the specified mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public ContainsFilenameFilter(String s, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/ContainsPathFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link PathFilter} matches paths that contain a specified string that can be located anywhere in the
* path.
*
* @author Maxence Bernard
*/
public class ContainsPathFilter extends AbstractContainsFilter implements PathFilter {
/**
* Creates a new case-insensitive ContainsPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
*/
public ContainsPathFilter(String s) {
this(s, false, false);
}
/**
* Creates a new ContainsPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
*/
public ContainsPathFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new ContainsPathFilter operating in the specified mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public ContainsPathFilter(String s, boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/CriterionFilter.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* CriterionFilter is a {@link FileFilter} that operates on a file criterion. It can be used to match
* paths without having to deal with {@link AbstractFile} instances. By extending {@link FileFilter}, this class can be
* used everywhere a FileFilter is accepted.
*
* true if this filter matched the given value, according to the current {@link #isInverted()}
* mode:
*
*
*
* @param value the value to test
* @return true if this filter matched the given value, according to the current inverted mode
*/
boolean match(C value);
/**
* Returns true if the given value was rejected by this filter, false if it was accepted.
*
* AbstractFile instances.
*
* @param value values to be tested
* @return an array of accepted AbstractFile instances
*/
C[] filter(C[] value);
/**
* Convenience method that returns true if all the values in the specified array were matched by
* {@link #match(Object)}, false if one of the values wasn't.
*
* @param value the values to be tested
* @return true if all the values in the specified array were accepted
*/
boolean match(C[] value);
/**
* Convenience method that returns true if all the values in the specified array were accepted by
* {@link #accept(Object)}, false if one of the values wasn't.
*
* @param value the values to be tested
* @return true if all the values in the specified array were accepted
*/
boolean accept(C[] value);
/**
* Convenience method that returns true if all the values in the specified array were rejected by
* {@link #reject(Object)}, false if one of the values wasn't.
*
* @param value the values to be tested
* @return true if all the values in the specified array were rejected
*/
boolean reject(C[] value);
/**
* Returns true if the given value was accepted by this filter, false if it was rejected.
*
* @param value the value to test
* @return true if the given value was accepted by this filter, false if it was rejected
*/
boolean accept(C value);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/CriterionValueGenerator.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* This interface defines a {@link #getCriterionValue(AbstractFile)} method that generates a criterion value for
* a specified {@link AbstractFile}. It is used by {@link CriterionFilter} to match files based on their criteria
* values.
*
* @see CriterionFilter
* @author Maxence Bernard
*/
public interface CriterionValueGeneratorEndsWithFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
*/
public EndsWithFilenameFilter(String s) {
this(s, false, false);
}
/**
* Creates a new EndsWithFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public EndsWithFilenameFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new EndsWithFilenameFilter operating in the specified mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public EndsWithFilenameFilter(String s, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/EndsWithPathFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link PathFilter} matches paths that end with a specified string.
*
* @author Maxence Bernard
*/
public class EndsWithPathFilter extends AbstractEndsWithFilter implements PathFilter {
/**
* Creates a new case-insensitive EndsWithPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
*/
public EndsWithPathFilter(String s) {
this(s, false, false);
}
/**
* Creates a new EndsWithPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
*/
public EndsWithPathFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new EndsWithPathFilter operating in the specified mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public EndsWithPathFilter(String s, boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/EqualsFilenameFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link FilenameFilter} matches filenames that are equal to a specified string.
*
* @author Maxence Bernard
*/
public class EqualsFilenameFilter extends AbstractEqualsFilter implements FilenameFilter {
/**
* Creates a new case-insensitive EqualsFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
*/
public EqualsFilenameFilter(String s) {
this(s, false, false);
}
/**
* Creates a new EqualsFilenameFilter operating in non-inverted mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public EqualsFilenameFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new EqualsFilenameFilter operating in the specified mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public EqualsFilenameFilter(String s, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/EqualsPathFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link PathFilter} matches paths that are equal to a specified string.
*
* @author Maxence Bernard
*/
public class EqualsPathFilter extends AbstractEqualsFilter implements PathFilter {
/**
* Creates a new case-insensitive EqualsPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
*/
public EqualsPathFilter(String s) {
this(s, false, false);
}
/**
* Creates a new EqualsPathFilter operating in non-inverted mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
*/
public EqualsPathFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new EqualsPathFilter operating in the specified mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public EqualsPathFilter(String s, boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/ExtensionFilenameFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link FilenameFilter} matches files whose path end with one of several specified extensions.
*
* ExtensionFilenameFilter matching filenames ending with the specified
* extension and operating in non-inverted mode.
*
* @param extension the extension to match
*/
public ExtensionFilenameFilter(String extension) {
this(extension, false, false);
}
/**
* Creates a ExtensionFilenameFilter matching filenames ending with the specified extension
* and operating in the specified modes.
*
* @param extension the extension to match
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public ExtensionFilenameFilter(String extension, boolean caseSensitive, boolean inverted) {
this(new String[]{extension}, caseSensitive, inverted);
}
/**
* Creates a case-insensitive ExtensionFilenameFilter matching filenames ending with one of the
* specified extensions and operating in the specified mode.
*
* @param ext the extensions to match
*/
public ExtensionFilenameFilter(String[] ext) {
this(ext, false, false);
}
/**
* Creates a new ExtensionFilenameFilter matching filenames ending with one of the specified
* extensions and operating in the specified modes.
*
* @param ext the extensions to match
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public ExtensionFilenameFilter(String[] ext, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), ext, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/ExtensionPathFilter.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see ExtensionPathFilter matching paths ending with the specified
* extension and operating in non-inverted mode.
*
* @param extension the extension to match
*/
public ExtensionPathFilter(String extension) {
this(extension, false, false);
}
/**
* Creates a ExtensionPathFilter matching paths ending with the specified extension
* and operating in the specified modes.
*
* @param extension the extension to match
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
private ExtensionPathFilter(String extension, boolean caseSensitive, boolean inverted) {
this(new String[]{extension}, caseSensitive, inverted);
}
/**
* Creates a case-insensitive ExtensionPathFilter matching paths ending with one of the
* specified extensions and operating in the specified mode.
*
* @param ext the extensions to match
*/
public ExtensionPathFilter(String[] ext) {
this(ext, false, false);
}
/**
* Creates a new ExtensionPathFilter matching paths ending with one of the specified
* extensions and operating in the specified modes.
*
* @param ext the extensions to match
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public ExtensionPathFilter(String[] ext, boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), ext, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/FileFilter.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.util.FileSet;
/**
* A FileFilter matches files that meet certain criteria. It can operate in two opposite modes: inverted
* and non-inverted. By default, a FileFilter operates in non-inverted mode where
* {@link #match(AbstractFile)} returns the value of {@link #accept(AbstractFile)}. On the contrary, when operating in
* inverted mode, {@link #match(AbstractFile)} returns the value of {@link #reject(AbstractFile)}. It is important to
* understand that {@link #accept(AbstractFile)} and {@link #reject(AbstractFile)} are not affected by the inverted
* mode in which a filter operates.
*
* FileFilter instance can be passed to {@link AbstractFile#ls(FileFilter)} to filter out some of the
* the files contained by a folder.
*
* @see AbstractFileFilter
* @see FilenameFilter
* @see com.mucommander.commons.file.AbstractFile#ls(FileFilter)
* @author Maxence Bernard
*/
public interface FileFilter {
/**
* Return true if this filter operates in normal mode, false if in inverted mode.
*
* @return true if this filter operates in normal mode, false if in inverted mode
*/
boolean isInverted();
/**
* Sets the mode in which {@link #match(com.mucommander.commons.file.AbstractFile)} operates. If true, this
* filter will operate in inverted mode: files that would be accepted by {@link #match(com.mucommander.commons.file.AbstractFile)}
* in normal (non-inverted) mode will be rejected, and vice-versa.
* The inverted mode has no effect on the values returned by {@link #accept(com.mucommander.commons.file.AbstractFile)} and
* {@link #reject(com.mucommander.commons.file.AbstractFile)}.
*
* @param inverted if true, this filter will operate in inverted mode.
*/
void setInverted(boolean inverted);
/**
* Returns true if this filter matched the given file, according to the current {@link #isInverted()}
* mode:
*
*
*
* @param file the file to test
* @return true if this filter matched the given file, according to the current inverted mode
*/
boolean match(AbstractFile file);
/**
* Returns true if the given file was rejected by this filter, false if it was accepted.
*
* AbstractFile instances.
*
* @param files files to be tested against {@link #match(com.mucommander.commons.file.AbstractFile)}
* @return a file array of files that were matched by this filter
*/
AbstractFile[] filter(AbstractFile files[]);
/**
* Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter
* and removes them from the given {@link FileSet}.
*
* @param files files to be tested against {@link #match(com.mucommander.commons.file.AbstractFile)}
*/
void filter(FileSet files);
/**
* Convenience method that returns true if all the files contained in the specified file array
* were matched by {@link #match(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified file array were matched by this filter
*/
boolean match(AbstractFile files[]);
/**
* Convenience method that returns true if all the files contained in the specified {@link FileSet}
* were matched by {@link #match(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified {@link FileSet} were matched by this filter
*/
boolean match(FileSet files);
/**
* Convenience method that returns true if all the files contained in the specified file array
* were accepted by {@link #accept(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified file array were accepted by this filter
*/
boolean accept(AbstractFile files[]);
/**
* Convenience method that returns true if all the files contained in the specified {@link FileSet}
* were accepted by {@link #accept(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified {@link FileSet} were accepted by this filter
*/
boolean accept(FileSet files);
/**
* Convenience method that returns true if all the files contained in the specified file array
* were rejected by {@link #reject(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified file array were rejected by this filter
*/
boolean reject(AbstractFile files[]);
/**
* Convenience method that returns true if all the files contained in the specified {@link FileSet}
* were rejected by {@link #reject(AbstractFile)}, false if one of the files wasn't.
*
* @param files the files to test against this FileFilter
* @return true if all the files contained in the specified {@link FileSet} were rejected by this filter
*/
boolean reject(FileSet files);
/**
* Returns true if the given file was accepted by this filter, false if it was rejected.
*
* OperationFileFilter matches files which support a specified {@link FileOperation file operation}.
*
* FilenameFilter is a {@link FileFilter} that operates on filenames.
*
* FilenameFilter can be passed to {@link AbstractFile#ls(FilenameFilter)} to filter out some of the
* files contained by a folder without creating the associated AbstractFile instances.
*
* @see AbstractFilenameFilter
* @author Maxence Bernard
*/
public interface FilenameFilter extends StringCriterionFilter {
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/FilenameGenerator.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* This interface specializes {@link CriterionValueGenerator} to have {@link #getCriterionValue(AbstractFile)} return
* the filename of the specified file.
*
* @author Maxence Bernard
*/
public class FilenameGenerator implements CriterionValueGeneratorOrFileFilter is a {@link ChainedFileFilter} that matches a file if one of its registered filters
* matches it.
*
* @author Maxence Bernard
*/
public class OrFileFilter extends ChainedFileFilter {
/**
* Creates a new OrFileFilter operating in non-inverted mode and containing the specified filters,
* if any.
*
* @param filters filters to add to this chained filter.
*/
public OrFileFilter(FileFilter... filters) {
this(false, filters);
}
/**
* Creates a new OrFileFilter operating in the specified mode and containing the specified filters,
* if any.
*
* @param inverted if true, this filter will operate in inverted mode.
* @param filters filters to add to this chained filter.
*/
public OrFileFilter(boolean inverted, FileFilter... filters) {
super(inverted, filters);
}
/**
* Calls {@link #match(com.mucommander.commons.file.AbstractFile)} on each of the registered filters, and returns
* true if one of them matched the given file, false if none of them did.
*
* true.
*
* @param file the file to test against the registered filters
* @return if the file was matched by one filter, false if none of them did
*/
@Override
public boolean accept(AbstractFile file) {
for (FileFilter filter : filters) {
if (filter.match(file)) {
return true;
}
}
return filters.isEmpty();
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/PassThroughFileFilter.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* PassThroughFileFilter is a filter that {@link #accept(com.mucommander.commons.file.AbstractFile) accepts} all
* files. Depending on the {@link #isInverted() inverted} mode, this filter will match all files or no file at all.
*
* @author Maxence Bernard
*/
public class PassThroughFileFilter extends AbstractFileFilter {
/**
* Creates a new PassThroughFileFilter operating in non-inverted mode.
*/
public PassThroughFileFilter() {
this(false);
}
/**
* Creates a new PassThroughFileFilter operating in the specified mode.
*
* @param inverted if true, this filter will operate in inverted mode.
*/
public PassThroughFileFilter(boolean inverted) {
super(inverted);
}
@Override
public boolean accept(AbstractFile file) {
return true;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/PathFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* PathFilter is a {@link FileFilter} that operates on absolute file paths.
*
* @see AbstractPathFilter
* @author Maxence Bernard
*/
public interface PathFilter extends StringCriterionFilter {
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/PathGenerator.java
================================================
package com.mucommander.commons.file.filter;
import com.mucommander.commons.file.AbstractFile;
/**
* This interface specializes {@link CriterionValueGenerator} to have {@link #getCriterionValue(AbstractFile)} return
* the absolute path of the specified file.
*
* @author Maxence Bernard
*/
public class PathGenerator implements CriterionValueGeneratorRegexpFilenameFilter matching the specified regexp and operating in non-inverted
* mode.
*
* @param regexp regular expression that matches string values.
* @param caseSensitive whether the regular expression is case-sensitive or not.
* @throws PatternSyntaxException if the syntax of the regular expression is not correct.
*/
public RegexpFilenameFilter(String regexp, boolean caseSensitive) throws PatternSyntaxException {
super(new FilenameGenerator(), regexp, caseSensitive, false);
}
/**
* Creates a new RegexpFilenameFilter matching the specified regexp and operating in the specified
* modes.
*
* @param regexp regular expression that matches string values.
* @param caseSensitive whether the regular expression is case-sensitive or not.
* @param inverted if true, this filter will operate in inverted mode.
* @throws PatternSyntaxException if the syntax of the regular expression is not correct.
*/
public RegexpFilenameFilter(String regexp, boolean caseSensitive, boolean inverted) throws PatternSyntaxException {
super(new FilenameGenerator(), regexp, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/RegexpPathFilter.java
================================================
package com.mucommander.commons.file.filter;
import java.util.regex.PatternSyntaxException;
/**
* This {@link PathFilter} that accepts or rejects files whose path match a specific regular expression.
*
* @author Maxence Bernard
*/
public class RegexpPathFilter extends AbstractRegexpFilter implements PathFilter {
/**
* Creates a new RegexpPathFilter matching the specified regexp and operating in non-inverted
* mode.
*
* @param regexp regular expression that matches string values.
* @param caseSensitive whether the regular expression is case-sensitive or not.
* @throws PatternSyntaxException if the syntax of the regular expression is not correct.
*/
public RegexpPathFilter(String regexp, boolean caseSensitive) throws PatternSyntaxException {
super(new PathGenerator(), regexp, caseSensitive, false);
}
/**
* Creates a new RegexpPathFilter matching the specified regexp and operating in the specified
* modes.
*
* @param regexp regular expression that matches string values.
* @param caseSensitive whether the regular expression is case-sensitive or not.
* @param inverted if true, this filter will operate in inverted mode.
* @throws PatternSyntaxException if the syntax of the regular expression is not correct.
*/
public RegexpPathFilter(String regexp, boolean caseSensitive, boolean inverted) throws PatternSyntaxException {
super(new PathGenerator(), regexp, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/StartsWithFilenameFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link FilenameFilter} matches filenames that start with a specified string.
*
* @author Maxence Bernard
*/
public class StartsWithFilenameFilter extends AbstractStartsWithFilter implements FilenameFilter {
/**
* Creates a new case-insensitive StartsFilenameFilter matching filenames starting with the specified
* string and operating in the specified mode.
*
* @param s the string to compare filenames against
*/
public StartsWithFilenameFilter(String s) {
this(s, false, false);
}
/**
* Creates a new StartsFilenameFilter matching filenames starting with the specified string and
* operating in non-inverted mode.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public StartsWithFilenameFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new StartsFilenameFilter matching filenames starting with the specified string and
* operating in the specified modes.
*
* @param s the string to compare filenames against
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public StartsWithFilenameFilter(String s, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/StartsWithPathFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* This {@link PathFilter} matches paths that start with a specified string.
*
* @author Maxence Bernard
*/
public class StartsWithPathFilter extends AbstractStartsWithFilter implements PathFilter {
/**
* Creates a new case-insensitive StartsPathFilter matching paths starting with the specified
* string and operating in the specified mode.
*
* @param s the string to compare paths against
*/
public StartsWithPathFilter(String s) {
this(s, false, false);
}
/**
* Creates a new StartsPathFilter matching paths starting with the specified string and
* operating in non-inverted mode.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
*/
public StartsWithPathFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new StartsPathFilter matching paths starting with the specified string and
* operating in the specified modes.
*
* @param s the string to compare paths against
* @param caseSensitive if true, this PathFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public StartsWithPathFilter(String s, boolean caseSensitive, boolean inverted) {
super(new PathGenerator(), s, caseSensitive, inverted);
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/StringCriterionFilter.java
================================================
package com.mucommander.commons.file.filter;
/**
* @author Maxence Bernard
*/
public interface StringCriterionFilter extends CriterionFiltertrue if this CriterionFilter is case-sensitive.
*
* @return true if this CriterionFilter is case-sensitive.
*/
boolean isCaseSensitive();
/**
* Specifies whether this CriterionFilter should be case-sensitive or not when comparing paths.
*
* @param caseSensitive if true, this CriterionFilter will be case-sensitive
*/
void setCaseSensitive(boolean caseSensitive);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/filter/WildcardFileFilter.java
================================================
/*
* This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander
* Copyright (C) 2013-2016 Oleg Trifonov
*
* trolCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* trolCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see WildcardFileFilter operating in non-inverted mode.
*
* @param s the wildcard to match
*/
public WildcardFileFilter(String s) {
this(s, false, false);
}
/**
* Creates a new WildcardFileFilter operating in non-inverted mode.
*
* @param s the wildcard to match
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
*/
public WildcardFileFilter(String s, boolean caseSensitive) {
this(s, caseSensitive, false);
}
/**
* Creates a new WildcardFileFilter operating in the specified mode.
*
* @param s the wildcard to match
* @param caseSensitive if true, this FilenameFilter will be case-sensitive
* @param inverted if true, this filter will operate in inverted mode.
*/
public WildcardFileFilter(String s, boolean caseSensitive, boolean inverted) {
super(new FilenameGenerator(), caseSensitive, inverted);
this.fileFilter = org.apache.commons.io.filefilter.WildcardFileFilter.builder().setWildcards(s).setIoCase(isCaseSensitive() ? IOCase.SENSITIVE : IOCase.INSENSITIVE).get();
}
@Override
public boolean accept(String value) {
return fileFilter.accept(new File(value));
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/icon/CacheableFileIconProvider.java
================================================
package com.mucommander.commons.file.icon;
import com.mucommander.commons.file.AbstractFile;
import javax.swing.*;
import java.awt.*;
/**
* CacheableFileIconProvider is an interface to be implemented by file icon providers that wish to use
* some icon caching to improve performance. This interface is to be used in conjunction with {@link CachedFileIconProvider}
* to form a functional cached provider.
*
* @author Maxence Bernard
*/
public interface CacheableFileIconProvider extends FileIconProvider {
/**
* Returns true if the icon cache can be used for the specified file and preferred resolution. This
* method allows the icon cache to be used only for certain types of files and/or preferred resolutions.
*
* true is returned, the icon cache will be looked up with
* {@link #lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} and if the cache did not return
* an icon, the icon will be added to the cache with {@link #addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon, java.awt.Dimension)}.
*
* On the other hand, if false is returned, {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* will simply be called, without querying or adding to the cache.
*
* @param file the file for which to retrieve an icon
* @param preferredResolution the preferred resolution for the icon
* @return true if the icon cache can be used with the specified file
*/
boolean isCacheable(AbstractFile file, Dimension preferredResolution);
/**
* This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* to perform a cache lookup and give implementations a chance to re-use a cached icon. If a non-null value is
* returned, the returned icon will be used.
*
* On the other hand, if null is returned, the icon will be fetched using {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* followed by a call to {@link #addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon,java.awt.Dimension)}
* to add the freshly-retrieved icon to the cache.
*
* true.
*
* @param file the file for which to look for a previously cached icon
* @param preferredResolution the preferred resolution for the icon
* @return a cached icon to re-use, null if there is none
*/
Icon lookupCache(AbstractFile file, Dimension preferredResolution);
/**
* This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* to give implementations a chance to cache an icon fetched with {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* and have it returned later by {@link #lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}.
*
* There is no obligation for this method to cache the given icon, implementations may freely choose whether to
* cache certain icons only.
*
* true.
*
* @param file the file that corresponds to the given icon
* @param icon the icon to add to the cache
* @param preferredResolution the preferred icon resolution that was originally requested
*/
void addToCache(AbstractFile file, Icon icon, Dimension preferredResolution);
void cleanCache();
}
================================================
FILE: src/main/java/com/mucommander/commons/file/icon/CachedFileIconProvider.java
================================================
package com.mucommander.commons.file.icon;
import com.mucommander.bookmark.Bookmark;
import com.mucommander.bookmark.BookmarkManager;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import com.mucommander.ui.icon.FileIcons;
import javax.swing.*;
import java.awt.*;
/**
* CachedFileIconProvider is a FileIconProvider with caching capabilities.
*
*
* When an icon is requested, a cache lookup is performed. If a cached value is found, it is returned. If not, the icon
* is fetched from the underlying provider and added to the cache.
*
* @author Maxence Bernard
*/
public class CachedFileIconProvider implements FileIconProvider {
/** The underlying icon provider and cache manager */
protected CacheableFileIconProvider cacheableFip;
/**
* Creates a new CachedFileIconProvider that uses the given {@link CacheableFileIconProvider} to access the cache
* and retrieve the icons.
*
* @param cacheableFip the underlying icon provider and cache manager
*/
public CachedFileIconProvider(CacheableFileIconProvider cacheableFip) {
this.cacheableFip = cacheableFip;
}
/**
* Creates and returns a {@link IconCache} instance.
*
* @return a new {@link IconCache} instance
*/
public static IconCache createCache() {
return new IconCache();
}
/**
* Implementation notes: this method first calls {@link CacheableFileIconProvider#isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}
* to determine if the icon cache is used.
*
* CacheableFileIconProvider
* to retrieve the icon. This icon is then added to the cache by calling
* {@link CacheableFileIconProvider#addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon, java.awt.Dimension)}.
*
* CacheableFileIconProvider and its value returned.
*/
@Override
public Icon getFileIcon(AbstractFile file, Dimension preferredResolution) {
boolean isCacheable = cacheableFip.isCacheable(file, preferredResolution);
if (BookmarkManager.isBookmark(file)) {
for (Bookmark bookmark : BookmarkManager.getBookmarks()) {
if (file.getName().equals(bookmark.getName())) {
// Note: if several bookmarks match current folder, the first one will be used
file = FileFactory.getFile(bookmark.getLocation());
//return getFileIcon(file, preferredResolution);
return FileIcons.getFileIcon(file, preferredResolution);
}
}
}
// Look for the file icon in the provider's cache
Icon icon = isCacheable ? cacheableFip.lookupCache(file, preferredResolution) : null;
// Icon is not cacheable or isn't present in the cache, retrieve it from the provider
if (icon == null) {
icon = cacheableFip.getFileIcon(file, preferredResolution);
// Cache the icon
if (isCacheable && icon != null) {
cacheableFip.addToCache(file, icon, preferredResolution);
}
}
return icon;
}
}
================================================
FILE: src/main/java/com/mucommander/commons/file/icon/FileIconProvider.java
================================================
package com.mucommander.commons.file.icon;
import com.mucommander.commons.file.AbstractFile;
import javax.swing.*;
import java.awt.*;
/**
* FileIconProvider provides a standard interface for retrieving file icons. Icon providers can fetch icons from any
* type of source, whether they be some API or a set of icon images. There is no requirement on the resolution in which
* the icons are provided, nor on the set of file criteria used to select the icon (file kind, name, size, ...).
* Implementations should however be able to return icons for any type of {@link AbstractFile}.
* The specialized {@link com.mucommander.commons.file.icon.LocalFileIconProvider} class can come in handy for icon sources
* that can only cope with local files.
*
* @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()
* @author Maxence Bernard
*/
public interface FileIconProvider {
/**
* Returns an icon for the given file, or null if it couldn't be retrieved, either because the
* given file doesn't exist or for any other reason.
*
* Dimension is used as a hint at the preferred icon's resolution; there is
* absolutely no guarantee that the returned Icon will indeed have this resolution. This dimension is
* only used to choose between different resolutions should more than one resolution be available, and return the
* one that most closely matches the specified one.
* The implementation is not expected to perform any rescaling (either up or down), this method should only return
* icons in their 'native' resolutions, using the preferred resolution to choose between different native dimensions.
* For example, if this provider is able to create icons both in 16x16 and 32x32 resolutions, and a 48x48 resolution
* is preferred, the 32x32 resolution should be favored as it is closer to 32x32.
*
* @param file the AbstractFile instance for which an icon is requested
* @param preferredResolution the preferred icon resolution
* @return an icon for the requested file
*/
Icon getFileIcon(AbstractFile file, Dimension preferredResolution);
}
================================================
FILE: src/main/java/com/mucommander/commons/file/icon/IconCache.java
================================================
/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2010 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see Object keys onto {@link Icon} instances.
* Any kind of Object may be used as the key: a file, a URL, an extension, ... allowing different of icon caching
* strategies to be implemented.
*
* Apache Commons Collection library.
* All accesses to the underlying map is synchronized, making this cache thread-safe.
*
* @author Maxence Bernard
*/
public class IconCache {
/** The actual hash map */
private final ReferenceMap