blob: 862143c97457332926a06e102e189673ac0379f0 [file] [log] [blame]
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.resources;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* ParallelListResourceBundle is another variant of ListResourceBundle
* supporting "parallel" contents provided by another resource bundle
* (OpenListResourceBundle). Parallel contents, if any, are added into this
* bundle on demand.
*
* @author Masayoshi Okutsu
*/
public abstract class ParallelListResourceBundle extends ResourceBundle {
private volatile ConcurrentMap<String, Object> lookup;
private volatile Set<String> keyset;
private final AtomicMarkableReference<Object[][]> parallelContents
= new AtomicMarkableReference<>(null, false);
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
protected ParallelListResourceBundle() {
}
/**
* Returns an array in which each item is a pair of objects in an
* Object array. The first element of each pair is the key, which
* must be a String, and the second element is the value
* associated with that key. See the class description for
* details.
*
* @return an array of an Object array representing a key-value pair.
*/
protected abstract Object[][] getContents();
/**
* Returns the parent of this resource bundle or null if there's no parent.
*
* @return the parent or null if no parent
*/
ResourceBundle getParent() {
return parent;
}
/**
* Sets the parallel contents to the data given by rb. If rb is null, this
* bundle will be marked as `complete'.
*
* @param rb an OpenResourceBundle for parallel contents, or null indicating
* there are no parallel contents for this bundle
*/
public void setParallelContents(OpenListResourceBundle rb) {
if (rb == null) {
parallelContents.compareAndSet(null, null, false, true);
} else {
parallelContents.compareAndSet(null, rb.getContents(), false, false);
}
}
/**
* Returns true if any parallel contents have been set or if this bundle is
* marked as complete.
*
* @return true if any parallel contents have been processed
*/
boolean areParallelContentsComplete() {
// Quick check for `complete'
if (parallelContents.isMarked()) {
return true;
}
boolean[] done = new boolean[1];
Object[][] data = parallelContents.get(done);
return data != null || done[0];
}
@Override
protected Object handleGetObject(String key) {
if (key == null) {
throw new NullPointerException();
}
loadLookupTablesIfNecessary();
return lookup.get(key);
}
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(keySet());
}
@Override
public boolean containsKey(String key) {
return keySet().contains(key);
}
@Override
protected Set<String> handleKeySet() {
loadLookupTablesIfNecessary();
return lookup.keySet();
}
@Override
@SuppressWarnings("UnusedAssignment")
public Set<String> keySet() {
Set<String> ks;
while ((ks = keyset) == null) {
ks = new KeySet(handleKeySet(), parent);
synchronized (this) {
if (keyset == null) {
keyset = ks;
}
}
}
return ks;
}
/**
* Discards any cached keyset value. This method is called from
* LocaleData for re-creating a KeySet.
*/
synchronized void resetKeySet() {
keyset = null;
}
/**
* Loads the lookup table if they haven't been loaded already.
*/
void loadLookupTablesIfNecessary() {
ConcurrentMap<String, Object> map = lookup;
if (map == null) {
map = new ConcurrentHashMap<>();
for (Object[] item : getContents()) {
map.put((String) item[0], item[1]);
}
}
// If there's any parallel contents data, merge the data into map.
Object[][] data = parallelContents.getReference();
if (data != null) {
for (Object[] item : data) {
map.putIfAbsent((String) item[0], item[1]);
}
parallelContents.set(null, true);
}
if (lookup == null) {
synchronized (this) {
if (lookup == null) {
lookup = map;
}
}
}
}
/**
* This class implements the Set interface for
* ParallelListResourceBundle methods.
*/
private static class KeySet extends AbstractSet<String> {
private final Set<String> set;
private final ResourceBundle parent;
private KeySet(Set<String> set, ResourceBundle parent) {
this.set = set;
this.parent = parent;
}
@Override
public boolean contains(Object o) {
if (set.contains(o)) {
return true;
}
return (parent != null) ? parent.containsKey((String) o) : false;
}
@Override
public Iterator<String> iterator() {
if (parent == null) {
return set.iterator();
}
return new Iterator<String>() {
private Iterator<String> itr = set.iterator();
private boolean usingParent;
@Override
public boolean hasNext() {
if (itr.hasNext()) {
return true;
}
if (!usingParent) {
Set<String> nextset = new HashSet<>(parent.keySet());
nextset.removeAll(set);
itr = nextset.iterator();
usingParent = true;
}
return itr.hasNext();
}
@Override
public String next() {
if (hasNext()) {
return itr.next();
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
if (parent == null) {
return set.size();
}
Set<String> allset = new HashSet<>(set);
allset.addAll(parent.keySet());
return allset.size();
}
}
}