├── .gitignore ├── .travis.yml ├── src ├── main │ └── java │ │ └── org │ │ └── freedesktop │ │ ├── DesktopEntryReader.java │ │ ├── DesktopEntryWriter.java │ │ ├── IniStyleFileWriter.java │ │ ├── IniStyleFileReader.java │ │ ├── DesktopEntry.java │ │ ├── BaseDirectory.java │ │ └── IniStyleFile.java └── test │ └── java │ └── org │ └── freedesktop │ ├── IniStyleFileWriterTest.java │ ├── IniStyleFileReaderTest.java │ ├── DesktopEntryTest.java │ ├── BaseDirectoryTest.java │ └── IniStyleFileTest.java ├── pom.xml ├── CONTRIBUTING.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk7 4 | - openjdk8 5 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/DesktopEntryReader.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | 6 | public class DesktopEntryReader { 7 | 8 | private IniStyleFileReader delegate; 9 | 10 | public DesktopEntryReader(BufferedReader reader) { 11 | delegate = new IniStyleFileReader(reader); 12 | } 13 | 14 | public DesktopEntry read() throws IOException { 15 | return new DesktopEntry(delegate.read()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/DesktopEntryWriter.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | public class DesktopEntryWriter { 7 | 8 | private IniStyleFileWriter delegate; 9 | 10 | /** 11 | * @param writer the writer must be using a utf-8 encoding 12 | */ 13 | public DesktopEntryWriter(Writer writer) { 14 | this.delegate = new IniStyleFileWriter(writer); 15 | } 16 | 17 | public void write(DesktopEntry file) throws IOException { 18 | delegate.write(file); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 4.0.0 8 | 9 | org.freedesktop 10 | xdg-java 11 | 0.0.1-SNAPSHOT 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 4.10 22 | 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-compiler-plugin 30 | 3.1 31 | 32 | 1.7 33 | 1.7 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/IniStyleFileWriter.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | import java.util.Map; 6 | 7 | public class IniStyleFileWriter { 8 | private final Writer writer; 9 | 10 | /** 11 | * @param writer the writer must be using a utf-8 encoding 12 | */ 13 | public IniStyleFileWriter(Writer writer) { 14 | this.writer = writer; 15 | } 16 | 17 | public void write(IniStyleFile file) throws IOException { 18 | writeGroup(file, DesktopEntry.GROUP_DEFAULT); 19 | for (String name : file.data.keySet()) { 20 | if (!name.equals(DesktopEntry.GROUP_DEFAULT)) { 21 | writeGroup(file, name); 22 | } 23 | } 24 | } 25 | 26 | private void writeGroup(IniStyleFile file, String groupName) throws IOException { 27 | if (!file.data.containsKey(groupName)) { 28 | return; 29 | } 30 | 31 | writer.write("[" + groupName + "]\n"); 32 | Map group = file.data.get(groupName); 33 | for (String key : group.keySet()) { 34 | writer.write(key + "=" + group.get(key) + "\n"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | This is a rough guide for contributing. 5 | 6 | To contribute a bugfix: 7 | - Create a pull request on github 8 | - Create one pull request per bug 9 | - Include as much information about your bug as possible, preferably a 10 | unit test or a stand alone minimal reproducer 11 | 12 | If you are looking to contribute features please create a pull 13 | request. If you are looking for contribution ideas, the following 14 | specifications are excellent candidates: 15 | 16 | - The [Desktop Menu Specification](https://www.freedesktop.org/wiki/Specifications/menu-spec/) 17 | - The [Desktop Trash Can Specification](https://www.freedesktop.org/wiki/Specifications/trash-spec/) 18 | - The [Icon Theme Specification](http://www.freedesktop.org/wiki/Specifications/icon-theme-spec/) 19 | - The [Shared MIME Info Database](https://www.freedesktop.org/wiki/Specifications/shared-mime-info-spec/) 20 | - The [Recent File Storage Specification](https://www.freedesktop.org/wiki/Specifications/recent-file-spec/) 21 | 22 | Code Guidelines 23 | --------------- 24 | 25 | This is a Java 7 project (for now). Please avoid using features only 26 | available in newer Java versions. I am thinking about switching to 27 | Java 8, any feedback about this is appreciated. 28 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/IniStyleFileReader.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Allows reading Desktop Entry files. 8 | */ 9 | public class IniStyleFileReader { 10 | /* TODO: preserve comments */ 11 | 12 | private BufferedReader reader; 13 | 14 | public IniStyleFileReader(BufferedReader reader) { 15 | this.reader = reader; 16 | } 17 | 18 | public IniStyleFile read() throws IOException { 19 | 20 | IniStyleFile iniStyleFile = new IniStyleFile(); 21 | String line = null; 22 | String groupName = null; 23 | while ((line = reader.readLine()) != null) { 24 | line = line.trim(); 25 | if (line.startsWith("[") && line.endsWith("]")) { 26 | groupName = line.substring(1, line.length() - 1); 27 | iniStyleFile.addGroup(groupName); 28 | } else if (groupName != null) { 29 | String[] parts = line.split("="); 30 | String key = parts[0].trim(); 31 | String value = ""; 32 | if (parts.length > 1) { 33 | value = parts[1].trim(); 34 | } 35 | iniStyleFile.add(groupName, key, value); 36 | } 37 | } 38 | 39 | return iniStyleFile; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/freedesktop/IniStyleFileWriterTest.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.IOException; 7 | import java.io.StringWriter; 8 | 9 | import org.junit.Test; 10 | 11 | public class IniStyleFileWriterTest { 12 | 13 | @Test 14 | public void testSimpleWriting() throws IOException { 15 | StringWriter w = new StringWriter(); 16 | IniStyleFileWriter writer = new IniStyleFileWriter(w); 17 | writer.write(new IniStyleFile("temp")); 18 | String result = w.getBuffer().toString(); 19 | assertNotNull(result); 20 | assertEquals("", result); 21 | } 22 | 23 | @Test 24 | public void testWritingWithOneGroup() throws IOException { 25 | StringWriter w = new StringWriter(); 26 | IniStyleFileWriter writer = new IniStyleFileWriter(w); 27 | IniStyleFile file = new IniStyleFile("group"); 28 | file.add("key", "value"); 29 | writer.write(file); 30 | String result = w.getBuffer().toString(); 31 | assertNotNull(result); 32 | assertEquals("[group]\nkey=value\n", result); 33 | } 34 | 35 | @Test 36 | public void testWritingWithOneGroupReplacingExistingKeys() throws IOException { 37 | StringWriter w = new StringWriter(); 38 | IniStyleFileWriter writer = new IniStyleFileWriter(w); 39 | IniStyleFile file = new IniStyleFile("group"); 40 | file.add("key", "value"); 41 | file.add("key", "new-value"); 42 | writer.write(file); 43 | String result = w.getBuffer().toString(); 44 | assertNotNull(result); 45 | assertEquals("[group]\nkey=new-value\n", result); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `xdg-java` 2 | ========== 3 | 4 | This is a Java API that provides an easy to use implementations of 5 | various 6 | [Free Desktop specifications](https://www.freedesktop.org/wiki/Specifications/). 7 | Use the correct application specific configuration, data and cache 8 | directories. Safely read and write desktop entries without worrying 9 | about parsing and escaping. 10 | 11 | Specifications 12 | -------------- 13 | 14 | The following specifications are currently implemented: 15 | 16 | ### [Base Directory Specification](https://www.freedesktop.org/wiki/Specifications/basedir-spec/) 17 | 18 | Use this to access standard directories for data, cache and 19 | configuration. 20 | 21 | Here's how to find the path to a cache file with the name `myCache`: 22 | 23 | File cacheFile = new File(BaseDirectory.get(BaseDirectory.XDG_CACHE_HOME), "myCache"); 24 | 25 | 26 | ### The [Desktop Entry Specification](http://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/) 27 | 28 | Use this to read and write desktop entries, with full validation. 29 | 30 | Here's how to read a desktop entry named `some.desktop`: 31 | 32 | BufferedReader reader = new BufferedReader(new FileReader("some.desktop")); 33 | DesktopEntry entry = new DesktopEntryReader(reader).read(); 34 | System.out.println("Name: " + entry.get(DesktopEntry.KEY_NAME)); 35 | 36 | 37 | Install 38 | ------- 39 | 40 | Build and install using maven: 41 | 42 | $ mvn clean install 43 | 44 | 45 | If you are using maven, add a dependency to your `pom.xml` file: 46 | 47 | 48 | org.freedesktop 49 | xdg-java 50 | 0.0.1-SNAPSHOT 51 | 52 | 53 | If you are using gradle, add a runtime dependency to your 54 | `build.gradle` file: 55 | 56 | runtime "org.freedesktop:xdg-java:0.0.1-SNAPSHOT@jar" 57 | 58 | For Ivy, add the following dependency to your `ivy.xml` file: 59 | 60 | 61 | 62 | 63 | Contributing 64 | ------------ 65 | See `CONTRIBUTING.md` for details. 66 | 67 | 68 | License 69 | ------- 70 | 71 | LGPL v2. Please see the `LICENSE` file for details. 72 | 73 | © Copyright Omair Majid 74 | -------------------------------------------------------------------------------- /src/test/java/org/freedesktop/IniStyleFileReaderTest.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.StringReader; 11 | 12 | import org.junit.Test; 13 | 14 | public class IniStyleFileReaderTest { 15 | 16 | @Test 17 | public void testMinimalRead() throws IOException { 18 | String input = "[Desktop Entry]\n" + 19 | "Name=Foo\n"; 20 | StringReader reader = new StringReader(input); 21 | IniStyleFileReader fileReader = new IniStyleFileReader(new BufferedReader(reader)); 22 | IniStyleFile file = fileReader.read(); 23 | assertNotNull(file); 24 | 25 | // check groups 26 | assertFalse(file.getGroupNames().isEmpty()); 27 | assertEquals(1, file.getGroupNames().size()); 28 | assertEquals("Desktop Entry", file.getGroupNames().toArray(new String[0])[0]); 29 | 30 | // check default group 31 | assertFalse(file.containsKey("Name")); 32 | assertEquals(null, file.get("Name")); 33 | 34 | // check group-values 35 | assertTrue(file.containsGroup("Desktop Entry")); 36 | assertTrue(file.containsKey("Desktop Entry", "Name")); 37 | assertEquals("Foo", file.get("Desktop Entry", "Name")); 38 | } 39 | 40 | @Test 41 | public void testBasicValidInput() throws IOException { 42 | String input = "" + 43 | "[Desktop Entry]\n" + 44 | "Version=1.0\n" + 45 | "Type=Application\n" + 46 | "Name=Foo Viewer\n" + 47 | "Comment=The best viewer for Foo objects available!\n" + 48 | "TryExec=fooview\n" + 49 | "Exec=fooview %F\n" + 50 | "Icon=fooview\n" + 51 | "MimeType=image/x-foo;\n" + 52 | "X-KDE-Library=libfooview\n" + 53 | "X-KDE-FactoryName=fooviewfactory\n" + 54 | "X-KDE-ServiceType=FooService\n"; 55 | StringReader reader = new StringReader(input); 56 | IniStyleFileReader fileReader = new IniStyleFileReader(new BufferedReader(reader)); 57 | IniStyleFile file = fileReader.read(); 58 | assertNotNull(file); 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/org/freedesktop/DesktopEntryTest.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.StringReader; 10 | import java.util.Locale; 11 | 12 | import org.junit.Test; 13 | 14 | public class DesktopEntryTest { 15 | 16 | @Test 17 | public void testConstructor() { 18 | DesktopEntry entry = new DesktopEntry(); 19 | assertNotNull(entry); 20 | } 21 | 22 | @Test 23 | public void testConstructor2() { 24 | IniStyleFile source = new IniStyleFile(); 25 | assertNotNull(source); 26 | DesktopEntry entry = new DesktopEntry(source); 27 | assertNotNull(entry); 28 | } 29 | 30 | public DesktopEntry createBasicDesktopEntry() throws IOException { 31 | String input = "" + 32 | "[Desktop Entry]\n" + 33 | "Version=1.0\n" + 34 | "Type=Application\n" + 35 | "Name=Foo Viewer\n" + 36 | "Name[en_CA]=en_CA Foo Viewer\n" + 37 | "NAME=Not the actual name - ignored\n" + 38 | "Comment=The best viewer for Foo objects available!\n" + 39 | "TryExec=fooview\n" + 40 | "Exec=fooview %F\n" + 41 | "Icon=fooview\n" + 42 | "MimeType=image/x-foo;\n" + 43 | "X-KDE-Library=libfooview\n" + 44 | "X-KDE-FactoryName=fooviewfactory\n" + 45 | "X-KDE-ServiceType=FooService\n" + 46 | "[Another Group]\n" + 47 | "Version=1.1\n" + 48 | "Icon=icon\n"; 49 | DesktopEntry entry = new DesktopEntry(new IniStyleFileReader(new BufferedReader(new StringReader(input))).read()); 50 | return entry; 51 | } 52 | 53 | @Test 54 | public void testBasicParsing() throws IOException { 55 | DesktopEntry entry = createBasicDesktopEntry(); 56 | assertTrue(entry.containsGroup(DesktopEntry.GROUP_REQUIRED)); 57 | 58 | assertTrue(entry.containsKey(DesktopEntry.KEY_VERSION)); 59 | assertEquals("1.0", entry.get(DesktopEntry.KEY_VERSION)); 60 | assertTrue(entry.containsKey(DesktopEntry.KEY_TYPE)); 61 | assertEquals("Application", entry.get(DesktopEntry.KEY_TYPE)); 62 | assertTrue(entry.containsKey(DesktopEntry.KEY_NAME)); 63 | assertEquals("Foo Viewer", entry.get(DesktopEntry.KEY_NAME)); 64 | 65 | assertTrue(entry.containsKey(DesktopEntry.KEY_COMMENT)); 66 | 67 | assertTrue(entry.containsKey(DesktopEntry.KEY_EXEC)); 68 | } 69 | 70 | @Test 71 | public void testExtensionsPresent() throws IOException { 72 | DesktopEntry entry = createBasicDesktopEntry(); 73 | assertTrue(entry.containsKey(DesktopEntry.GROUP_REQUIRED, "X-KDE-Library")); 74 | assertTrue(entry.containsKey(DesktopEntry.GROUP_REQUIRED, "X-KDE-FactoryName")); 75 | assertTrue(entry.containsKey(DesktopEntry.GROUP_REQUIRED, "X-KDE-ServiceType")); 76 | } 77 | 78 | @Test 79 | public void testLocalizedNames() throws IOException { 80 | DesktopEntry entry = createBasicDesktopEntry(); 81 | String value; 82 | value = entry.getLocalizedValue(DesktopEntry.KEY_NAME, new Locale("en", "CA")); 83 | assertEquals("en_CA Foo Viewer", value); 84 | 85 | value = entry.getLocalizedValue(DesktopEntry.KEY_NAME, new Locale("en")); 86 | assertEquals("Foo Viewer", value); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/DesktopEntry.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * An interface to the Desktop Entry Specification. 7 | * 8 | * @version 1.1 9 | */ 10 | public class DesktopEntry extends IniStyleFile { 11 | 12 | public static final String EXTENSION_FILE = ".desktop"; 13 | public static final String EXTENSION_DIRECTORY = ".directory"; 14 | public static final String FILE_CHARSET = "UTF-8"; 15 | 16 | public static final String GROUP_REQUIRED = "Desktop Entry"; 17 | public static final String GROUP_DEFAULT = GROUP_REQUIRED; 18 | 19 | public static final String KEY_TYPE = "Type"; 20 | public static final String KEY_VERSION = "Version"; 21 | public static final String KEY_NAME = "Name"; 22 | public static final String KEY_GENERIC_NAME = "GenericName"; 23 | public static final String KEY_NO_DISPLAY = "NoDisplay"; 24 | public static final String KEY_COMMENT = "Comment"; 25 | public static final String KEY_ICON = "Icon"; 26 | public static final String KEY_HIDDEN = "Hidden"; 27 | public static final String KEY_ONLY_SHOW_IN = "OnlyShowIn"; 28 | public static final String KEY_NOT_SHOW_IN = "NotShowIn"; 29 | public static final String KEY_DBUS_ACTIVATABLE = "DBusActivatable"; 30 | public static final String KEY_TRY_EXEC = "TryExec"; 31 | public static final String KEY_EXEC = "Exec"; 32 | public static final String KEY_PATH = "Path"; 33 | public static final String KEY_TERMINAL = "Terminal"; 34 | public static final String KEY_ACTIONS = "Actions"; 35 | public static final String KEY_MIME_TYPE = "MimeType"; 36 | public static final String KEY_CATEGORIES = "Categories"; 37 | public static final String KEY_KEYWORDS = "Keywords"; 38 | public static final String KEY_STARTUP_NOTIFY = "StartupNotify"; 39 | public static final String KEY_STARTUP_WM_CLASS = "StartupWMClass"; 40 | public static final String KEY_URL = "URL"; 41 | 42 | public DesktopEntry() { 43 | super(GROUP_DEFAULT); 44 | 45 | initKnownTypes(); 46 | } 47 | 48 | public DesktopEntry(IniStyleFile source) { 49 | super(source, GROUP_DEFAULT); 50 | 51 | initKnownTypes(); 52 | } 53 | 54 | private void initKnownTypes() { 55 | addKnownType(GROUP_DEFAULT, KEY_TYPE, ValueType.STRING); 56 | addKnownType(GROUP_DEFAULT, KEY_VERSION, ValueType.STRING); 57 | addKnownType(GROUP_DEFAULT, KEY_NAME, ValueType.LOCALE_STRING); 58 | addKnownType(GROUP_DEFAULT, KEY_GENERIC_NAME, ValueType.LOCALE_STRING); 59 | addKnownType(GROUP_DEFAULT, KEY_NO_DISPLAY, ValueType.BOOLEAN); 60 | addKnownType(GROUP_DEFAULT, KEY_COMMENT, ValueType.LOCALE_STRING); 61 | addKnownType(GROUP_DEFAULT, KEY_ICON, ValueType.LOCALE_STRING); 62 | addKnownType(GROUP_DEFAULT, KEY_HIDDEN, ValueType.BOOLEAN); 63 | addKnownType(GROUP_DEFAULT, KEY_ONLY_SHOW_IN, ValueType.STRINGS); 64 | addKnownType(GROUP_DEFAULT, KEY_NOT_SHOW_IN, ValueType.STRINGS); 65 | addKnownType(GROUP_DEFAULT, KEY_DBUS_ACTIVATABLE, ValueType.BOOLEAN); 66 | addKnownType(GROUP_DEFAULT, KEY_TRY_EXEC, ValueType.STRING); 67 | addKnownType(GROUP_DEFAULT, KEY_EXEC, ValueType.STRING); 68 | addKnownType(GROUP_DEFAULT, KEY_PATH, ValueType.STRING); 69 | addKnownType(GROUP_DEFAULT, KEY_TERMINAL, ValueType.BOOLEAN); 70 | addKnownType(GROUP_DEFAULT, KEY_ACTIONS, ValueType.STRINGS); 71 | addKnownType(GROUP_DEFAULT, KEY_MIME_TYPE, ValueType.STRINGS); 72 | addKnownType(GROUP_DEFAULT, KEY_CATEGORIES, ValueType.STRINGS); 73 | addKnownType(GROUP_DEFAULT, KEY_KEYWORDS, ValueType.LOCALE_STRING); 74 | addKnownType(GROUP_DEFAULT, KEY_STARTUP_NOTIFY, ValueType.BOOLEAN); 75 | addKnownType(GROUP_DEFAULT, KEY_STARTUP_WM_CLASS, ValueType.STRING); 76 | addKnownType(GROUP_DEFAULT, KEY_URL, ValueType.STRING); 77 | } 78 | 79 | public String getLocalizedValue(String key, Locale locale) { 80 | return getLocalizedValue(defaultGroup, key, locale); 81 | } 82 | 83 | public String getLocalizedValue(String group, String key, Locale locale) { 84 | String suffix = locale.getLanguage(); 85 | if (!locale.getCountry().equals("")) { 86 | suffix = suffix + "_" + locale.getCountry(); 87 | } 88 | String result; 89 | 90 | result = get(group, key + "[" + suffix + "]"); 91 | if (result != null) { 92 | return result; 93 | } 94 | 95 | suffix = locale.getLanguage(); 96 | result = get(group, key + "[" + suffix + "]"); 97 | if (result != null) { 98 | return result; 99 | } 100 | 101 | result = get(group, key); 102 | return result; 103 | } 104 | 105 | @Override 106 | protected void checkAllValid() { 107 | super.checkAllValid(); 108 | // TODO add more 109 | } 110 | 111 | @Override 112 | protected void checkValidKeyValue(String group, String key, String value) { 113 | super.checkValidKeyValue(group, key, value); 114 | 115 | if (group.equals(GROUP_DEFAULT) && key.equals(KEY_EXEC)) { 116 | checkValidExecValue(value); 117 | } 118 | } 119 | 120 | private void checkValidExecValue(String value) { 121 | // TODO implement 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/BaseDirectory.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | 6 | /** 7 | * A simple way to utilize the Base Directory Specification 8 | * 9 | * @version 0.7 10 | */ 11 | public class BaseDirectory { 12 | 13 | /** 14 | * base directory relative to which user-specific non-essential (cached) 15 | * data should be written 16 | */ 17 | public static final String XDG_CACHE_HOME = "XDG_CACHE_HOME"; 18 | 19 | /** 20 | * base directory relative to which user-specific configuration files should 21 | * be written 22 | */ 23 | public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; 24 | 25 | /** 26 | * set of preference ordered base directories relative to which 27 | * configuration files should be searched. The directories are separated by 28 | * by {@link File#pathSeparator} 29 | */ 30 | public static final String XDG_CONFIG_DIRS = "XDG_CONFIG_DIRS"; 31 | 32 | /** 33 | * base directory relative to which user-specific data files should be 34 | * written 35 | */ 36 | public static final String XDG_DATA_HOME = "XDG_DATA_HOME"; 37 | 38 | /** 39 | * set of preference ordered base directories relative to which data files 40 | * should be searched. Directories are separated by {@link File#pathSeparator} 41 | */ 42 | public static final String XDG_DATA_DIRS = "XDG_DATA_DIRS"; 43 | 44 | /** 45 | * base directory relative to which user-specific runtime files and other 46 | * file objects should be placed. May be {@code null}. 47 | */ 48 | public static final String XDG_RUNTIME_DIR = "XDG_RUNTIME_DIR"; 49 | 50 | private static Map environment = System.getenv(); 51 | 52 | /** 53 | * Get the base directory or set of base directories defined by the 54 | * specification. 55 | * 56 | * @param name one of the XDG_* constants defined in {@link BaseDirectory} 57 | * @return the directory (or the set of directories) as a string. For the 58 | * exact return type, see the docs for the constant. May be null if the 59 | * standard does not specify a default and the associated environment 60 | * variable(s) are undefined. 61 | */ 62 | public static String get(String name) { 63 | switch (name) { 64 | case XDG_CACHE_HOME: 65 | return getCacheHome(); 66 | case XDG_CONFIG_HOME: 67 | return getConfigHome(); 68 | case XDG_CONFIG_DIRS: 69 | return getConfigDirs(); 70 | case XDG_DATA_HOME: 71 | return getDataHome(); 72 | case XDG_DATA_DIRS: 73 | return getDataDirs(); 74 | case XDG_RUNTIME_DIR: 75 | return getRuntimeDir(); 76 | default: 77 | return null; 78 | } 79 | } 80 | 81 | /** This is meant only for testing */ 82 | static void setEnvironment(Map env) { 83 | environment = env; 84 | } 85 | 86 | private static String getCacheHome() { 87 | String value = environment.get(XDG_CACHE_HOME); 88 | if (value == null || value.trim().length() == 0) { 89 | String XDG_CACHE_HOME_DEFAULT = environment.get("HOME") + File.separator + ".cache"; 90 | value = XDG_CACHE_HOME_DEFAULT; 91 | } 92 | return value; 93 | } 94 | 95 | private static String getConfigHome() { 96 | String value = environment.get(XDG_CONFIG_HOME); 97 | if (value == null || value.trim().length() == 0) { 98 | String XDG_CONFIG_HOME_DEFAULT = environment.get("HOME") + File.separator + ".config"; 99 | value = XDG_CONFIG_HOME_DEFAULT; 100 | } 101 | return value; 102 | } 103 | 104 | private static String getConfigDirs() { 105 | String value = environment.get(XDG_CONFIG_DIRS); 106 | if (value == null || value.trim().length() == 0) { 107 | String XDG_CONFIG_DIRS_DEFAULT = File.separator + "etc" + File.separator + "xdg"; 108 | value = XDG_CONFIG_DIRS_DEFAULT; 109 | } 110 | return value; 111 | } 112 | 113 | private static String getDataHome() { 114 | String value = environment.get(XDG_DATA_HOME); 115 | if (value == null || value.trim().length() == 0) { 116 | String XDG_DATA_HOME_DEFAULT = environment.get("HOME") + 117 | File.separator + ".local" + File.separator + "share"; 118 | value = XDG_DATA_HOME_DEFAULT; 119 | } 120 | return value; 121 | } 122 | 123 | private static String getDataDirs() { 124 | String value = environment.get(XDG_DATA_DIRS); 125 | if (value == null || value.trim().length() == 0) { 126 | String XDG_DATA_DIRS_DEFAULT = File.separator + "usr" + File.separator + "local" + File.separator + "share" + File.separator; 127 | XDG_DATA_DIRS_DEFAULT = XDG_DATA_DIRS_DEFAULT + File.pathSeparator; 128 | XDG_DATA_DIRS_DEFAULT = XDG_DATA_DIRS_DEFAULT + File.separator + "usr" + File.separator + "share" + File.separator; 129 | value = XDG_DATA_DIRS_DEFAULT; 130 | } 131 | return value; 132 | } 133 | 134 | private static String getRuntimeDir() { 135 | String value = environment.get(XDG_RUNTIME_DIR); 136 | return value; 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/org/freedesktop/BaseDirectoryTest.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | public class BaseDirectoryTest { 14 | 15 | private Map buildCustomEnvironment() { 16 | Map environment = new HashMap<>(); 17 | environment.put("HOME", "${HOME}"); 18 | return environment; 19 | } 20 | 21 | @Before 22 | public void setUp() { 23 | BaseDirectory.setEnvironment(System.getenv()); 24 | } 25 | 26 | @After 27 | public void tearDown() { 28 | BaseDirectory.setEnvironment(System.getenv()); 29 | } 30 | 31 | @Test 32 | public void testUnknown() { 33 | String unknownVar = BaseDirectory.get("UNKNOWN_AND_IMPOSSIBLE_XDG_VARIABLE"); 34 | assertEquals(null, unknownVar); 35 | } 36 | 37 | @Test 38 | public void testDataHomeWithEnvSet() { 39 | Map env = buildCustomEnvironment(); 40 | env.put("XDG_DATA_HOME", "${XDG_DATA_HOME}"); 41 | BaseDirectory.setEnvironment(env); 42 | String dataHome = BaseDirectory.get(BaseDirectory.XDG_DATA_HOME); 43 | assertNotNull(dataHome); 44 | assertEquals("${XDG_DATA_HOME}", dataHome); 45 | } 46 | 47 | @Test 48 | public void testDataHomeDefault() { 49 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 50 | String dataHome = BaseDirectory.get(BaseDirectory.XDG_DATA_HOME); 51 | assertNotNull(dataHome); 52 | assertEquals("${HOME}/.local/share", dataHome); 53 | } 54 | 55 | @Test 56 | public void testConfigHomeWithEnvSet() { 57 | Map env = buildCustomEnvironment(); 58 | env.put("XDG_CONFIG_HOME", "${XDG_CONFIG_HOME}"); 59 | BaseDirectory.setEnvironment(env); 60 | String dir = BaseDirectory.get(BaseDirectory.XDG_CONFIG_HOME); 61 | assertNotNull(dir); 62 | assertEquals("${XDG_CONFIG_HOME}", dir); 63 | } 64 | 65 | @Test 66 | public void testConfigHomeDefault() { 67 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 68 | String configHome = BaseDirectory.get(BaseDirectory.XDG_CONFIG_HOME); 69 | assertNotNull(configHome); 70 | assertEquals("${HOME}/.config", configHome); 71 | } 72 | 73 | @Test 74 | public void testDataDirsWithEnvSet() { 75 | Map env = buildCustomEnvironment(); 76 | env.put("XDG_DATA_DIRS", "${XDG_DATA_DIRS}"); 77 | BaseDirectory.setEnvironment(env); 78 | String dataDirs = BaseDirectory.get(BaseDirectory.XDG_DATA_DIRS); 79 | assertNotNull(dataDirs); 80 | assertEquals("${XDG_DATA_DIRS}", dataDirs); 81 | } 82 | 83 | @Test 84 | public void testDataDirsDefault() { 85 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 86 | String dataDirs = BaseDirectory.get(BaseDirectory.XDG_DATA_DIRS); 87 | assertNotNull(dataDirs); 88 | assertEquals("/usr/local/share/:/usr/share/", dataDirs); 89 | } 90 | 91 | @Test 92 | public void testConfigDirsWithEnvSet() { 93 | Map env = buildCustomEnvironment(); 94 | env.put("XDG_CONFIG_DIRS", "${XDG_CONFIG_DIRS}"); 95 | BaseDirectory.setEnvironment(env); 96 | String configDirs = BaseDirectory.get(BaseDirectory.XDG_CONFIG_DIRS); 97 | assertNotNull(configDirs); 98 | assertEquals("${XDG_CONFIG_DIRS}", configDirs); 99 | } 100 | 101 | @Test 102 | public void testConfigDirsDefault() { 103 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 104 | String configDirs = BaseDirectory.get(BaseDirectory.XDG_CONFIG_DIRS); 105 | assertNotNull(configDirs); 106 | assertEquals("/etc/xdg", configDirs); 107 | 108 | } 109 | 110 | @Test 111 | public void testCacheHomeWithEnvSet() { 112 | Map env = buildCustomEnvironment(); 113 | env.put("XDG_CACHE_HOME", "${XDG_CACHE_HOME}"); 114 | BaseDirectory.setEnvironment(env); 115 | String dir = BaseDirectory.get(BaseDirectory.XDG_CACHE_HOME); 116 | assertNotNull(dir); 117 | assertEquals("${XDG_CACHE_HOME}", dir); 118 | } 119 | 120 | @Test 121 | public void testCacheHomeDefault() { 122 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 123 | String dir = BaseDirectory.get(BaseDirectory.XDG_CACHE_HOME); 124 | assertNotNull(dir); 125 | assertEquals("${HOME}/.cache", dir); 126 | } 127 | 128 | @Test 129 | public void testRuntimeDirWithEnvSet() { 130 | Map env = buildCustomEnvironment(); 131 | env.put("XDG_RUNTIME_DIR", "${XDG_RUNTIME_DIR}"); 132 | BaseDirectory.setEnvironment(env); 133 | String runtimeDir = BaseDirectory.get(BaseDirectory.XDG_RUNTIME_DIR); 134 | assertNotNull(runtimeDir); 135 | assertEquals("${XDG_RUNTIME_DIR}", runtimeDir); 136 | } 137 | 138 | @Test 139 | public void testRuntimeDirWithoutEnvSet() { 140 | BaseDirectory.setEnvironment(buildCustomEnvironment()); 141 | String runtimeDir = BaseDirectory.get(BaseDirectory.XDG_RUNTIME_DIR); 142 | assertEquals(null, runtimeDir); 143 | } 144 | 145 | @Test 146 | public void testRuntimeDirMatchesSystemEnv() { 147 | BaseDirectory.setEnvironment(System.getenv()); 148 | String runtimeDir = BaseDirectory.get(BaseDirectory.XDG_RUNTIME_DIR); 149 | assertEquals(System.getenv("XDG_RUNTIME_DIR"), runtimeDir); 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/org/freedesktop/IniStyleFile.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.Set; 7 | import java.util.TreeSet; 8 | 9 | public class IniStyleFile { 10 | 11 | public enum ValueType { 12 | LOCALE_STRING, 13 | STRINGS, 14 | STRING, 15 | INTEGERS, 16 | INTEGER, 17 | BOOLEAN, 18 | POINTS, 19 | } 20 | 21 | protected final Map> data; 22 | protected final Map> knownTypes; 23 | 24 | protected final String defaultGroup; 25 | 26 | public IniStyleFile() { 27 | this("default"); 28 | } 29 | 30 | public IniStyleFile(String defaultGroup) { 31 | this.defaultGroup = defaultGroup; 32 | this.data = new HashMap<>(); 33 | this.knownTypes = new HashMap<>(); 34 | } 35 | 36 | public IniStyleFile(IniStyleFile other) { 37 | this(other, other.defaultGroup); 38 | } 39 | 40 | public IniStyleFile(IniStyleFile other, String defaultGroup) { 41 | // FIXME deep copy the data 42 | this.defaultGroup = defaultGroup; 43 | this.data = other.data; 44 | this.knownTypes = other.knownTypes; 45 | checkAllValid(); 46 | } 47 | 48 | public void addGroup(String groupName) { 49 | checkValidGroupName(groupName); 50 | 51 | if (data.containsKey(groupName)) { 52 | return; 53 | } 54 | 55 | data.put(groupName, new HashMap()); 56 | 57 | } 58 | 59 | public void add(String group, String key, String value) { 60 | checkValidGroupName(group); 61 | checkValidKeyValue(group, key, value); 62 | 63 | if (!(data.containsKey(group))) { 64 | addGroup(group); 65 | } 66 | 67 | Map groupData = data.get(group); 68 | 69 | groupData.put(key, value); 70 | } 71 | 72 | /** 73 | * Add a key/value pair to the default group 74 | */ 75 | public void add(String key, String value) { 76 | add(defaultGroup, key, value); 77 | } 78 | 79 | public boolean containsGroup(String group) { 80 | return data.containsKey(group); 81 | } 82 | 83 | public boolean containsKey(String key) { 84 | return containsKey(defaultGroup, key); 85 | } 86 | 87 | public boolean containsKey(String group, String key) { 88 | if (!data.containsKey(group)) { 89 | return false; 90 | } 91 | return data.get(group).containsKey(key); 92 | } 93 | 94 | public String get(String group, String key) { 95 | if (data.containsKey(group)) { 96 | Map groupData = data.get(group); 97 | if (groupData.containsKey(key)) { 98 | return groupData.get(key); 99 | } 100 | } 101 | return null; 102 | } 103 | 104 | public String get(String key) { 105 | return get(defaultGroup, key); 106 | } 107 | 108 | protected String[] getAsList(String key) { 109 | String value = get(key); 110 | if (value == null) { 111 | return null; 112 | } 113 | String[] directories = value.split(","); 114 | // TODO check for empty elements and remove them 115 | return directories; 116 | } 117 | 118 | public void remove(String key) { 119 | remove(defaultGroup, key); 120 | } 121 | 122 | public void remove(String group, String key) { 123 | if (!data.containsKey(group)) { 124 | throw new IllegalArgumentException(); 125 | } 126 | 127 | if (!data.get(group).containsKey(key)) { 128 | throw new IllegalArgumentException(); 129 | } 130 | 131 | data.get(group).remove(key); 132 | } 133 | 134 | public String getDefaultGroupName() { 135 | return defaultGroup; 136 | } 137 | 138 | public Set getGroupNames() { 139 | return new TreeSet<>(data.keySet()); 140 | } 141 | 142 | public Map getGroup(String group) { 143 | if (!data.containsKey(group)) { 144 | return null; 145 | } 146 | 147 | return new HashMap<>(data.get(group)); 148 | } 149 | 150 | public void removeGroup(String groupName) { 151 | if (!data.containsKey(groupName)) { 152 | throw new IllegalArgumentException(); 153 | } 154 | 155 | data.remove(groupName); 156 | } 157 | 158 | protected void checkAllValid() { 159 | for (Entry> group : data.entrySet()) { 160 | String groupName = group.getKey(); 161 | checkValidGroupName(groupName); 162 | for (Entry entry : group.getValue().entrySet()) { 163 | checkValidKeyValue(groupName, entry.getKey(), entry.getValue()); 164 | } 165 | } 166 | } 167 | 168 | protected void checkValidGroupName(String group) { 169 | if (group == null) { 170 | throw new IllegalArgumentException("Group name is null"); 171 | } 172 | if (group.trim().length() == 0) { 173 | throw new IllegalArgumentException("Group name is empty"); 174 | } 175 | if (group.contains("[") || group.contains("]")) { 176 | throw new IllegalArgumentException("Group name contains invalid character"); 177 | } 178 | if (group.contains("\n")) { 179 | throw new IllegalArgumentException("Group name contains a newline"); 180 | } 181 | } 182 | 183 | protected void checkValidKeyValue(String group, String key, String value) { 184 | if (key == null || key.contains("\n") || key.contains("=") || key.trim().length() == 0) { 185 | throw new IllegalArgumentException(); 186 | } 187 | if (value == null || value.contains("\n")) { 188 | throw new IllegalArgumentException(); 189 | } 190 | if (knownTypes.containsKey(group)) { 191 | Map knownGroup = knownTypes.get(group); 192 | ValueType valueType = knownGroup == null ? null : knownGroup.get(key); 193 | if (valueType == ValueType.LOCALE_STRING) { 194 | // pass 195 | // FIXME newlines? 196 | } else { 197 | if (key.contains("[") || key.contains("]")) { 198 | throw new IllegalArgumentException("non-locale key contains '[' or ']'"); 199 | } 200 | } 201 | 202 | if (valueType == ValueType.BOOLEAN) { 203 | if (!isValidBoolean(value)) { 204 | throw new IllegalArgumentException("boolean is not true or false"); 205 | } 206 | } 207 | 208 | if (valueType == ValueType.INTEGER) { 209 | if (!isValidNumber(value)) { 210 | throw new IllegalArgumentException("type numeric is not a valid number"); 211 | } 212 | } 213 | 214 | } 215 | 216 | } 217 | 218 | protected boolean isValidBoolean(String value) { 219 | return value.toLowerCase().equals("true") || value.toLowerCase().equals("false"); 220 | } 221 | 222 | protected boolean isValidNumber(String value) { 223 | try { 224 | Double.parseDouble(value); 225 | return true; 226 | } catch (NumberFormatException notDouble) { 227 | return false; 228 | } 229 | 230 | } 231 | 232 | public void addKnownType(String group, String key, ValueType valueType) { 233 | Map groupType = knownTypes.get(group); 234 | if (groupType == null) { 235 | groupType = new HashMap<>(); 236 | knownTypes.put(group, groupType); 237 | } 238 | groupType.put(key, valueType); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/test/java/org/freedesktop/IniStyleFileTest.java: -------------------------------------------------------------------------------- 1 | package org.freedesktop; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import org.junit.Test; 12 | 13 | public class IniStyleFileTest { 14 | 15 | @Test 16 | public void testConstructor() { 17 | IniStyleFile entry = new IniStyleFile(); 18 | assertNotNull(entry); 19 | } 20 | 21 | @Test 22 | public void testConstructor2() { 23 | IniStyleFile file1 = new IniStyleFile(); 24 | file1.add("test", "test", "test"); 25 | 26 | IniStyleFile file2 = new IniStyleFile(file1); 27 | assertTrue(file2.containsKey("test", "test")); 28 | assertEquals("test", file2.get("test", "test")); 29 | } 30 | 31 | @Test 32 | public void testWithDefaultGroup() { 33 | IniStyleFile file = new IniStyleFile("default-group"); 34 | file.add("key", "value"); 35 | } 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | public void testAddNullGroupWithKeyValue() { 39 | IniStyleFile file = new IniStyleFile(); 40 | file.add(null, "key", "value"); 41 | } 42 | 43 | /* 44 | * Add group 45 | */ 46 | 47 | @Test 48 | public void testAddGroup() { 49 | IniStyleFile file = new IniStyleFile(); 50 | file.addGroup("test"); 51 | assertTrue(file.containsGroup("test")); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void testAddGroupWithNewLine() { 56 | IniStyleFile file = new IniStyleFile(); 57 | file.addGroup("test\nandmore"); 58 | } 59 | 60 | @Test(expected = IllegalArgumentException.class) 61 | public void testAddNullGroup() { 62 | IniStyleFile file = new IniStyleFile(); 63 | file.addGroup(null); 64 | } 65 | 66 | @Test 67 | public void testAddGroupMultipleTimes() { 68 | IniStyleFile file = new IniStyleFile(); 69 | file.addGroup("test"); 70 | assertTrue(file.containsGroup("test")); 71 | file.addGroup("test"); 72 | assertTrue(file.containsGroup("test")); 73 | } 74 | 75 | @Test(expected = IllegalArgumentException.class) 76 | public void testAddInvalidGroupName1() { 77 | IniStyleFile entry = new IniStyleFile(); 78 | entry.add("[", "foo", "bar"); 79 | } 80 | 81 | @Test(expected = IllegalArgumentException.class) 82 | public void testAddInvalidGroupName2() { 83 | IniStyleFile entry = new IniStyleFile(); 84 | entry.add("]", "foo", "bar"); 85 | } 86 | 87 | @Test(expected = IllegalArgumentException.class) 88 | public void testAddInvalidGroupName3() { 89 | IniStyleFile entry = new IniStyleFile(); 90 | entry.add(" ", "foo", "bar"); 91 | } 92 | 93 | @Test(expected = IllegalArgumentException.class) 94 | public void testAddInvalidGroupName4() { 95 | IniStyleFile file = new IniStyleFile(); 96 | file.add("\n", "foo", "bar"); 97 | } 98 | 99 | /* 100 | * Contains group 101 | */ 102 | 103 | @Test 104 | public void testContainsGroup() { 105 | IniStyleFile file = new IniStyleFile("default-group"); 106 | file.add("key", "value"); 107 | assertTrue(file.containsGroup("default-group")); 108 | } 109 | 110 | @Test 111 | public void testContainsNonExistingGroup() { 112 | IniStyleFile file = new IniStyleFile("default-group"); 113 | assertFalse(file.containsGroup("some-other-group")); 114 | } 115 | 116 | /* 117 | * Get group 118 | */ 119 | 120 | @Test 121 | public void testGetGroup() { 122 | IniStyleFile file = new IniStyleFile("default-group"); 123 | file.add("key", "value"); 124 | Map group = file.getGroup("default-group"); 125 | assertEquals(1, group.entrySet().size()); 126 | } 127 | 128 | @Test 129 | public void testGetGroupMissing() { 130 | IniStyleFile file = new IniStyleFile("default-group"); 131 | Map group = file.getGroup("some-random-group"); 132 | assertEquals(null, group); 133 | } 134 | 135 | @Test 136 | public void testGetDefaultGroupName() { 137 | IniStyleFile file = new IniStyleFile("default-group"); 138 | String groupName = file.getDefaultGroupName(); 139 | assertEquals("default-group", groupName); 140 | } 141 | 142 | @Test 143 | public void testGetDefaultGroupMissing() { 144 | IniStyleFile file = new IniStyleFile("default-group"); 145 | String defaultGroup = file.getDefaultGroupName(); 146 | Map group = file.getGroup(defaultGroup); 147 | assertEquals(null, group); 148 | } 149 | 150 | @Test 151 | public void testGetGroupNames() { 152 | IniStyleFile file = new IniStyleFile("default-group"); 153 | file.add("key", "value"); 154 | Set groupNames = file.getGroupNames(); 155 | assertFalse(groupNames.isEmpty()); 156 | assertEquals(1, groupNames.size()); 157 | assertEquals("default-group", groupNames.toArray(new String[0])[0]); 158 | } 159 | 160 | /* 161 | * Remove group 162 | */ 163 | 164 | @Test 165 | public void testRemoveGroup() { 166 | IniStyleFile file = new IniStyleFile("default-group"); 167 | file.add("new-group", "key", "value"); 168 | assertTrue(file.containsKey("new-group", "key")); 169 | file.removeGroup("new-group"); 170 | assertFalse(file.containsGroup("new-group")); 171 | } 172 | 173 | @Test(expected = IllegalArgumentException.class) 174 | public void testRemoveNullGroup() { 175 | IniStyleFile file = new IniStyleFile("default-group"); 176 | assertFalse(file.containsGroup(null)); 177 | file.removeGroup(null); 178 | } 179 | 180 | /* 181 | * Add key/value 182 | */ 183 | 184 | @Test 185 | public void testAddKeyValue() { 186 | IniStyleFile file = new IniStyleFile("default-group"); 187 | file.add("key", "value"); 188 | assertEquals("value", file.get("default-group", "key")); 189 | } 190 | 191 | @Test(expected = IllegalArgumentException.class) 192 | public void testAddInvalidKeyName() { 193 | IniStyleFile file = new IniStyleFile(); 194 | file.add("test", "=", "value"); 195 | } 196 | 197 | @Test(expected = IllegalArgumentException.class) 198 | public void testAddEmptyKeyName() { 199 | IniStyleFile file = new IniStyleFile(); 200 | file.add("test", "", "value"); 201 | } 202 | 203 | @Test 204 | public void testAddEmptyValue() { 205 | IniStyleFile file = new IniStyleFile(); 206 | file.add("test", "key", ""); 207 | } 208 | 209 | @Test(expected = IllegalArgumentException.class) 210 | public void testAddNullKeyName() { 211 | IniStyleFile file = new IniStyleFile(); 212 | file.add(null, "dummy"); 213 | } 214 | 215 | @Test(expected = IllegalArgumentException.class) 216 | public void testAddNewLines1() { 217 | IniStyleFile entry = new IniStyleFile(); 218 | entry.add("\n", "value"); 219 | } 220 | 221 | @Test(expected = IllegalArgumentException.class) 222 | public void testAddNewLines2() { 223 | IniStyleFile entry = new IniStyleFile(); 224 | entry.add("key", "\n"); 225 | } 226 | 227 | @Test(expected = IllegalArgumentException.class) 228 | public void testAddNewLines3() { 229 | IniStyleFile entry = new IniStyleFile(); 230 | entry.add("key\nkey", "value"); 231 | } 232 | 233 | @Test(expected = IllegalArgumentException.class) 234 | public void testAddNewLines4() { 235 | IniStyleFile entry = new IniStyleFile(); 236 | entry.add("key", "value\nvalue"); 237 | } 238 | 239 | @Test 240 | public void testAddingKnownLocaleStringValueType() { 241 | IniStyleFile entry = new IniStyleFile(); 242 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.LOCALE_STRING); 243 | entry.add("FOO", "BAR", "[en_US]"); 244 | } 245 | 246 | @Test 247 | public void testAddingKnownStringsValueType() { 248 | IniStyleFile entry = new IniStyleFile(); 249 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.STRINGS); 250 | entry.add("FOO", "BAR", "GNOME:UNITY"); 251 | } 252 | 253 | @Test 254 | public void testAddingKnownStringValueType() { 255 | IniStyleFile entry = new IniStyleFile(); 256 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.STRING); 257 | entry.add("FOO", "BAR", "foo"); 258 | } 259 | 260 | // TODO 261 | // @Test 262 | // public void testAddingKnownIntegersValueType() { 263 | // } 264 | 265 | // TODO 266 | // @Test(expected = IllegalArgumentException.class) 267 | // public void testKnownValueTypeRejectsBadIntegersValueType() { 268 | // } 269 | 270 | @Test 271 | public void testAddingKnownIntegerValueType() { 272 | IniStyleFile entry = new IniStyleFile(); 273 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.INTEGER); 274 | entry.add("FOO", "BAR", "10"); 275 | } 276 | 277 | @Test(expected = IllegalArgumentException.class) 278 | public void testKnownValueTypeRejectsBadIntegerValueType() { 279 | IniStyleFile entry = new IniStyleFile(); 280 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.INTEGER); 281 | entry.add("FOO", "BAR", "ten"); 282 | } 283 | 284 | @Test 285 | public void testAddingKnownBooleanValueType() { 286 | IniStyleFile entry = new IniStyleFile(); 287 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.BOOLEAN); 288 | entry.add("FOO", "BAR", "true"); 289 | } 290 | 291 | @Test(expected = IllegalArgumentException.class) 292 | public void testKnownValueTypeRejectsBadBooleanValueType() { 293 | IniStyleFile entry = new IniStyleFile(); 294 | entry.addKnownType("FOO", "BAR", IniStyleFile.ValueType.BOOLEAN); 295 | entry.add("FOO", "BAR", "falsy"); 296 | } 297 | 298 | // TODO 299 | // @Test 300 | // public void testAddingKnownPointsValueType() { 301 | // } 302 | 303 | // TODO 304 | // @Test(expected = IllegalArgumentException.class) 305 | // public void testKnownValueTypeRejectsBadPointsValueType() { 306 | // } 307 | 308 | /* 309 | * Contains key/value 310 | */ 311 | 312 | @Test 313 | public void testContainsKeyValue() { 314 | IniStyleFile file = new IniStyleFile("default-group"); 315 | file.add("key", "value"); 316 | assertTrue(file.containsKey("default-group", "key")); 317 | } 318 | 319 | @Test 320 | public void testContainsMissingKey() { 321 | IniStyleFile file = new IniStyleFile("default-group"); 322 | assertFalse(file.containsGroup(null)); 323 | assertFalse(file.containsKey("group", "key")); 324 | } 325 | 326 | /* 327 | * Get key/value 328 | */ 329 | 330 | @Test 331 | public void testGetKeyValueFromDefaultGroup() { 332 | IniStyleFile file = new IniStyleFile("default-group"); 333 | file.add("key", "value"); 334 | assertEquals("value", file.get("key")); 335 | } 336 | 337 | @Test 338 | public void testGetNonExistingKeyValueFromDefaultGroup() { 339 | IniStyleFile file = new IniStyleFile("default-group"); 340 | assertEquals(null, file.get("key")); 341 | } 342 | 343 | @Test 344 | public void testGetKeyValueFromGroup() { 345 | IniStyleFile file = new IniStyleFile(); 346 | file.add("group", "key", "value"); 347 | assertEquals("value", file.get("group", "key")); 348 | } 349 | 350 | @Test 351 | public void testGetNonExistingKeyValueFromGroup() { 352 | IniStyleFile file = new IniStyleFile(); 353 | assertEquals(null, file.get("group", "key")); 354 | } 355 | 356 | /* 357 | * Remove key/value 358 | */ 359 | 360 | @Test(expected = IllegalArgumentException.class) 361 | public void testRemoveNonExistingKey() { 362 | IniStyleFile file = new IniStyleFile(); 363 | file.remove("some-non-existing-key"); 364 | } 365 | 366 | @Test(expected = IllegalArgumentException.class) 367 | public void testRemoveNonExistingKey2() { 368 | IniStyleFile file = new IniStyleFile("default-group"); 369 | file.remove("default-group", "some-non-existing-key"); 370 | } 371 | 372 | @Test(expected = IllegalArgumentException.class) 373 | public void testRemoveNonExistingKey3() { 374 | IniStyleFile file = new IniStyleFile("default-group"); 375 | file.add("default-group", "another-key", "another-value"); 376 | file.remove("default-group", "some-non-existing-key"); 377 | } 378 | 379 | @Test 380 | public void testRemoveExistingKey() { 381 | IniStyleFile file = new IniStyleFile(); 382 | file.add("key", "value"); 383 | assertTrue(file.containsKey("key")); 384 | file.remove("key"); 385 | assertFalse(file.containsKey("key")); 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | --------------------------------------------------------------------------------