blob: ebcab98570fd99b76d173775b8130ecf0758c949 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.properties;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.openide.filesystems.FileObject;
import org.openide.loaders.ExtensionList;
import org.openide.loaders.MultiDataObject;
import org.openide.loaders.MultiFileLoader;
import org.openide.util.NbBundle;
import org.openide.util.io.SafeException;
/**
* Data loader which recognizes properties files.
* This class is final only for performance reasons,
* can be unfinaled if desired.
*
* @author Ian Formanek, Petr Jiricka
* @author Marian Petras
*/
public final class PropertiesDataLoader extends MultiFileLoader {
/** Extension for properties files. */
static final String PROPERTIES_EXTENSION = "properties"; // NOI18N
/** Properties MIME type*/
static final String PROPERTIES_MIME_TYPE = "text/x-properties"; //NOI18N
/** Character used to separate parts of bundle properties file name */
public static final char PRB_SEPARATOR_CHAR = '_';
/** Generated serial version UID. */
static final long serialVersionUID =4384899552891479449L;
/** name of property with extensions */
public static final String PROP_EXTENSIONS = "extensions"; // NOI18N
/** */
private Reference<PropertiesEncoding> encodingRef;
/** */
private static Set<String> knownLanguages;
/** */
private static Set<String> knownCountries;
/** */
private static boolean nestedView = false;
/** Creates new PropertiesDataLoader. */
public PropertiesDataLoader() {
super("org.netbeans.modules.properties.PropertiesDataObject"); // NOI18N
// Set extentions. Due performance reasons do it here instead in initialize method.
// During startup it's in findPrimaryFile method called getExtensions method. If the
// extentions list was not set in constructor the initialize method would be called
// during startup, but we want to avoid the initialize call since we don't need
// actions and display name initialized during startup time.
ExtensionList extList = new ExtensionList();
extList.addExtension(PROPERTIES_EXTENSION);
// Add .impl for CORBA module.
extList.addExtension("impl"); // NOI18N
setExtensions(extList);
}
/**
*/
PropertiesEncoding getEncoding() {
PropertiesEncoding encoding;
if ((encodingRef == null) || ((encoding = encodingRef.get()) == null)) {
encoding = new PropertiesEncoding();
encodingRef = new SoftReference<PropertiesEncoding>(encoding);
}
return encoding;
}
/** */
@Override
protected String defaultDisplayName() {
return NbBundle.getMessage(PropertiesDataLoader.class,
"PROP_PropertiesLoader_Name"); //NOI18N
}
/**
* This methods uses the layer action context so it returns
* a non-<code>null</code> value.
*
* @return name of the context on layer files to read/write actions to
*/
@Override
protected String actionsContext () {
return "Loaders/text/x-properties/Actions/"; //NOI18N
}
/**
* @return <code>PropertiesDataObject</code> for the specified
* <code>FileObject</code>
*/
protected MultiDataObject createMultiObject(final FileObject fo)
throws IOException {
return new PropertiesDataObject(fo, this);
}
/** */
protected FileObject findPrimaryFile (FileObject fo) {
if (fo.isFolder()) {
return null;
}
if (fo.getExt().equalsIgnoreCase(PROPERTIES_EXTENSION)) {
/*
* returns a file whose name is the shortest valid prefix
* corresponding to an existing file
*/
String fName = fo.getName();
if (nestedView) {
int index = fName.indexOf(PRB_SEPARATOR_CHAR);
while (index != -1) {
FileObject candidate = fo.getParent().getFileObject(
fName.substring(0, index), fo.getExt());
if (candidate != null && isValidLocaleSuffix(fName.substring(index))) {
return candidate;
}
index = fName.indexOf(PRB_SEPARATOR_CHAR, index + 1);
}
}
return fo;
} else if (getExtensions().isRegistered(fo)) {
return fo;
} else if (PROPERTIES_MIME_TYPE.equalsIgnoreCase(fo.getMIMEType(PROPERTIES_MIME_TYPE))) {
return fo;
} else {
return null;
}
}
private static boolean isValidLocaleSuffix(String s) {
// first char is _
int n = s.length();
String s1;
// check first suffix - language (two chars)
if (n == 3 || (n > 3 && s.charAt(3) == PRB_SEPARATOR_CHAR)) {
s1 = s.substring(1, 3).toLowerCase();
// language must be followed by a valid country suffix or no suffix
} else {
return false;
}
// check second suffix - country (two chars)
String s2;
if (n == 3) {
s2 = null;
} else if (n == 6 || (n > 6 && s.charAt(6) == PRB_SEPARATOR_CHAR)) {
s2 = s.substring(4, 6).toUpperCase();
// country may be followed by whatever additional suffix
} else {
return false;
}
if (knownLanguages == null) {
knownLanguages = new HashSet<String>(Arrays.asList(Locale.getISOLanguages()));
}
if (!knownLanguages.contains(s1)) {
return false;
}
if (s2 != null) {
if (knownCountries == null) {
knownCountries = new HashSet<String>(Arrays.asList(Locale.getISOCountries()));
}
if (!knownCountries.contains(s2)) {
return false;
}
}
return true;
}
/**
* @return <code>PropertiesFileEntry</code> for the given file
*/
protected MultiDataObject.Entry createPrimaryEntry(MultiDataObject obj,
FileObject primaryFile) {
return new PropertiesFileEntry(obj, primaryFile);
}
/**
* @return <code>PropertiesFileEntry</code> for the given file
*/
protected MultiDataObject.Entry createSecondaryEntry(
MultiDataObject obj,
FileObject secondaryFile) {
return new PropertiesFileEntry(obj, secondaryFile);
}
/**
* Sets the extension list for this data loader.
* This data loader will then recognize all files having any extension
* of the given list.
*
* @param ext list of extensions
*/
public void setExtensions(ExtensionList ext) {
putProperty(PROP_EXTENSIONS, ext, true);
}
/**
* Get the extension list for this data loader.
*
* @return list of extensions
* @see #setExtensions
*/
public ExtensionList getExtensions() {
ExtensionList l = (ExtensionList) getProperty(PROP_EXTENSIONS);
if (l == null) {
l = new ExtensionList();
putProperty(PROP_EXTENSIONS, l, false);
}
return l;
}
/** Writes extensions to the stream.
* @param oo ignored
*/
@Override
public void writeExternal (ObjectOutput oo) throws IOException {
super.writeExternal (oo);
oo.writeObject (getProperty (PROP_EXTENSIONS));
}
/** Reads nothing from the stream.
* @param oi ignored
*/
@Override
public void readExternal (ObjectInput oi)
throws IOException, ClassNotFoundException {
SafeException se;
try {
super.readExternal (oi);
se = null;
} catch (SafeException se2) {
se = se2;
}
setExtensions ((ExtensionList)oi.readObject ());
if (se != null) {
throw se;
}
}
}