git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@995 379699f6-c40d-0410-875b-85095c16579estable
parent
59c6451fa8
commit
8c31bdd062
@ -0,0 +1,6 @@ |
||||
package jode.obfuscator; |
||||
|
||||
public interface IdentifierMatcher { |
||||
public boolean matches(Identifier ident); |
||||
} |
||||
|
@ -0,0 +1,58 @@ |
||||
/* LocalIdentifier Copyright (C) 1999 Jochen Hoenicke. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2, or (at your option) |
||||
* any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; see the file COPYING. If not, write to |
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
* |
||||
* $Id$ |
||||
*/ |
||||
|
||||
package jode.obfuscator; |
||||
|
||||
public class LocalIdentifier extends Identifier { |
||||
String name; |
||||
String type; |
||||
|
||||
public LocalIdentifier(String name, String type, MethodIdentifier mIdent) { |
||||
super(name); |
||||
this.name = name; |
||||
this.type = type; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public String getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void applyPreserveRule(IdentifierMatcher preserveRule) { |
||||
} |
||||
|
||||
public Identifier getParent() { |
||||
return null; |
||||
} |
||||
|
||||
public String getFullName() { |
||||
return name; |
||||
} |
||||
|
||||
public String getFullAlias() { |
||||
return getAlias(); |
||||
} |
||||
|
||||
public boolean conflicting(String newAlias) { |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,233 @@ |
||||
package jode.obfuscator; |
||||
import java.lang.reflect.Modifier; |
||||
|
||||
public class ModifierMatcher implements IdentifierMatcher, Cloneable { |
||||
static final int PUBLIC = Modifier.PUBLIC; |
||||
static final int PROTECTED = Modifier.PROTECTED; |
||||
static final int PRIVATE = Modifier.PRIVATE; |
||||
|
||||
int[] andMasks; |
||||
int[] xorMasks; |
||||
|
||||
public static ModifierMatcher denyAll = new ModifierMatcher(new int[0], |
||||
new int[0]); |
||||
public static ModifierMatcher allowAll = new ModifierMatcher(0, 0); |
||||
|
||||
/* Invariants: |
||||
* \forall i: ~andMasks[i] & xorMasks[i] == 0 |
||||
* \forall i: entries wo. i does not imply entry nr. i |
||||
*/ |
||||
|
||||
private ModifierMatcher(int[] ands, int[] xors) { |
||||
andMasks = ands; |
||||
xorMasks = xors; |
||||
} |
||||
|
||||
public ModifierMatcher(int and, int xor) { |
||||
andMasks = new int[] { and }; |
||||
xorMasks = new int[] { xor }; |
||||
} |
||||
|
||||
private static boolean implies(int and1, int xor1, int and2, int xor2) { |
||||
return ((and1 & and2) == and2 && (xor1 & and2) == xor2); |
||||
} |
||||
|
||||
private boolean implies(int and, int xor) { |
||||
for (int i=0; i < andMasks.length; i++) { |
||||
if (!implies(andMasks[i], xorMasks[i], and, xor)) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private boolean impliedBy(int and, int xor) { |
||||
for (int i=0; i< andMasks.length; i++) { |
||||
if (implies(and, xor, andMasks[i], xorMasks[i])) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean implies(ModifierMatcher mm) { |
||||
for (int i=0; i < andMasks.length; i++) { |
||||
if (!mm.impliedBy(andMasks[i], xorMasks[i])) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public ModifierMatcher and(ModifierMatcher mm) { |
||||
if (implies(mm)) |
||||
return this; |
||||
if (mm.implies(this)) |
||||
return mm; |
||||
|
||||
ModifierMatcher result = denyAll; |
||||
for (int i=0; i< andMasks.length; i++) |
||||
result = result.or(mm.and(andMasks[i], xorMasks[i])); |
||||
return result; |
||||
} |
||||
|
||||
public ModifierMatcher or(ModifierMatcher mm) { |
||||
if (implies(mm)) |
||||
return mm; |
||||
if (mm.implies(this)) |
||||
return this; |
||||
ModifierMatcher result = this; |
||||
for (int i=0; i < mm.andMasks.length; i++) |
||||
result = result.or(mm.andMasks[i], mm.xorMasks[i]); |
||||
return result; |
||||
} |
||||
|
||||
private ModifierMatcher and(int and, int xor) { |
||||
if (this.implies(and, xor)) |
||||
return this; |
||||
int newCount = 0; |
||||
next_i: |
||||
for (int i=0; i < andMasks.length; i++) { |
||||
if (implies(and, xor, andMasks[i], xorMasks[i])) |
||||
continue next_i; |
||||
|
||||
for (int j=0; j<andMasks.length; j++) { |
||||
if (j != i |
||||
&& implies(and | andMasks[j], xor | xorMasks[j], |
||||
andMasks[i], xorMasks[i])) |
||||
continue next_i; |
||||
} |
||||
newCount++; |
||||
} |
||||
if (newCount == 0) |
||||
return new ModifierMatcher(and, xor); |
||||
int[] ands = new int[newCount]; |
||||
int[] xors = new int[newCount]; |
||||
int index = 0; |
||||
for (int i=0; i < newCount; i++) { |
||||
int bothAnd = andMasks[i] & and; |
||||
if ((xorMasks[i] & bothAnd) == (and & bothAnd)) { |
||||
ands[index++] = andMasks[i] | and; |
||||
xors[index++] = xorMasks[i] | xor; |
||||
} |
||||
} |
||||
return new ModifierMatcher(ands, xors); |
||||
} |
||||
|
||||
private ModifierMatcher or(int and, int xor) { |
||||
int matchIndex = -1; |
||||
if (this == denyAll) |
||||
return new ModifierMatcher(and, xor); |
||||
for (int i=0; i< andMasks.length; i++) { |
||||
if (implies(and, xor, andMasks[i], xorMasks[i])) |
||||
return this; |
||||
if (implies(andMasks[i], xorMasks[i], and, xor)) { |
||||
matchIndex = i; |
||||
break; |
||||
} |
||||
} |
||||
int[] ands, xors; |
||||
if (matchIndex == -1) { |
||||
matchIndex = andMasks.length; |
||||
ands = new int[matchIndex+1]; |
||||
xors = new int[matchIndex+1]; |
||||
System.arraycopy(andMasks, 0, ands, 0, matchIndex); |
||||
System.arraycopy(xorMasks, 0, xors, 0, matchIndex); |
||||
} else { |
||||
ands = (int[]) andMasks.clone(); |
||||
xors = (int[]) xorMasks.clone(); |
||||
} |
||||
ands[matchIndex] = and; |
||||
xors[matchIndex] = xor; |
||||
return new ModifierMatcher(ands, xors); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new ModifierMatcher, that matches only modifiers, we |
||||
* also match and also forces the access rights, to be accessModif |
||||
* (or less restrictive). |
||||
* @param accessModif the access modifier. Use 0 for package access, |
||||
* or Modifier.PRIVATE/PROTECTED/PUBLIC. |
||||
* @param andAbove allow to be less restrictive. |
||||
* @return a new modifier matcher that will also use the given accesses. |
||||
*/ |
||||
public ModifierMatcher forceAccess(int accessModif, boolean andAbove) { |
||||
if (andAbove) { |
||||
if (accessModif == Modifier.PRIVATE) |
||||
return this; |
||||
if (accessModif == 0) |
||||
return this.and(Modifier.PRIVATE, 0); |
||||
|
||||
ModifierMatcher result = this.and(Modifier.PUBLIC, PUBLIC); |
||||
if (accessModif == Modifier.PROTECTED) |
||||
return result |
||||
.or(this.and(Modifier.PROTECTED, Modifier.PROTECTED)); |
||||
if (accessModif == Modifier.PUBLIC) |
||||
return result; |
||||
throw new IllegalArgumentException(""+accessModif); |
||||
} else { |
||||
if (accessModif == 0) |
||||
return this.and(Modifier.PRIVATE | |
||||
Modifier.PROTECTED | Modifier.PUBLIC, 0); |
||||
else |
||||
return this.and(accessModif, accessModif); |
||||
} |
||||
} |
||||
|
||||
public ModifierMatcher forbidAccess(int accessModif, boolean andAbove) { |
||||
if (andAbove) { |
||||
if (accessModif == Modifier.PRIVATE) |
||||
// This forbids all access.
|
||||
return denyAll; |
||||
if (accessModif == 0) |
||||
return this.and(Modifier.PRIVATE, Modifier.PRIVATE); |
||||
if (accessModif == Modifier.PROTECTED) |
||||
return this.and(Modifier.PROTECTED | Modifier.PUBLIC, 0); |
||||
if (accessModif == Modifier.PUBLIC) |
||||
return this.and(Modifier.PUBLIC, 0); |
||||
throw new IllegalArgumentException(""+accessModif); |
||||
} else { |
||||
if (accessModif == 0) { |
||||
return this.and(Modifier.PRIVATE, Modifier.PRIVATE) |
||||
.or(this.and(Modifier.PROTECTED, Modifier.PROTECTED)) |
||||
.or(this.and(Modifier.PUBLIC, Modifier.PUBLIC)); |
||||
} else |
||||
return this.and(accessModif, 0); |
||||
} |
||||
} |
||||
|
||||
public final ModifierMatcher forceModifier(int modifier) { |
||||
return this.and(modifier, modifier); |
||||
} |
||||
|
||||
public final ModifierMatcher forbidModifier(int modifier) { |
||||
return this.and(modifier, 0); |
||||
} |
||||
|
||||
public final boolean matches(int modifiers) { |
||||
for (int i=0; i< andMasks.length; i++) |
||||
if ((modifiers & andMasks[i]) == xorMasks[i]) |
||||
return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public final boolean matches(Identifier ident) { |
||||
int modifiers; |
||||
/* XXX NEW INTERFACE OR ANOTHER METHOD IN IDENTIFIER? */ |
||||
if (ident instanceof ClassIdentifier) |
||||
modifiers = ((ClassIdentifier) ident).info.getModifiers(); |
||||
else if (ident instanceof MethodIdentifier) |
||||
modifiers = ((MethodIdentifier) ident).info.getModifiers(); |
||||
else if (ident instanceof FieldIdentifier) |
||||
modifiers = ((FieldIdentifier) ident).info.getModifiers(); |
||||
else |
||||
return false; |
||||
return matches(modifiers); |
||||
} |
||||
|
||||
public Object clone() { |
||||
try { |
||||
return super.clone(); |
||||
} catch (CloneNotSupportedException ex) { |
||||
throw new IncompatibleClassChangeError(getClass().getName()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,126 @@ |
||||
/* NameSwapper Copyright (C) 1999 Jochen Hoenicke. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2, or (at your option) |
||||
* any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; see the file COPYING. If not, write to |
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
* |
||||
* $Id$ |
||||
*/ |
||||
|
||||
package jode.obfuscator; |
||||
import java.util.Random; |
||||
|
||||
///#ifdef JDK12
|
||||
///import java.util.Collection;
|
||||
///import java.util.Set;
|
||||
///import java.util.HashSet;
|
||||
///import java.util.Iterator;
|
||||
///#else
|
||||
import jode.util.Collection; |
||||
import jode.util.Set; |
||||
import jode.util.HashSet; |
||||
import jode.util.Iterator; |
||||
///#endif
|
||||
|
||||
|
||||
public class NameSwapper implements Renamer { |
||||
private Random rand; |
||||
private Set packs, clazzes, methods, fields, locals; |
||||
|
||||
public NameSwapper(boolean swapAll, long seed) { |
||||
if (swapAll) { |
||||
packs = clazzes = methods = fields = locals = new HashSet(); |
||||
} else { |
||||
packs = new HashSet(); |
||||
clazzes = new HashSet(); |
||||
methods = new HashSet(); |
||||
fields = new HashSet(); |
||||
locals = new HashSet(); |
||||
} |
||||
} |
||||
|
||||
public NameSwapper(boolean swapAll) { |
||||
this(swapAll, System.currentTimeMillis()); |
||||
} |
||||
|
||||
private void addName(Collection coll, String name) { |
||||
coll.add(name); |
||||
} |
||||
|
||||
private String getName(Collection coll) { |
||||
///#ifdef JDK12
|
||||
/// int pos = rand.nextInt(coll.size());
|
||||
///#else
|
||||
int pos = rand.nextInt() % coll.size(); |
||||
///#endif
|
||||
Iterator i = coll.iterator(); |
||||
while (pos > 0) |
||||
i.next(); |
||||
return (String) i.next(); |
||||
} |
||||
|
||||
public final void addPackageName(String name) { |
||||
addName(packs, name); |
||||
} |
||||
|
||||
public final void addClassName(String name) { |
||||
addName(clazzes, name); |
||||
} |
||||
|
||||
public final void addMethodName(String name) { |
||||
addName(methods, name); |
||||
} |
||||
|
||||
public final void addFieldName(String name) { |
||||
addName(fields, name); |
||||
} |
||||
|
||||
public final void addLocalName(String name) { |
||||
addName(locals, name); |
||||
} |
||||
|
||||
public final String getPackageName() { |
||||
return getName(packs); |
||||
} |
||||
|
||||
public final String getClassName() { |
||||
return getName(clazzes); |
||||
} |
||||
|
||||
public final String getMethodName() { |
||||
return getName(methods); |
||||
} |
||||
|
||||
public final String getFieldName() { |
||||
return getName(fields); |
||||
} |
||||
|
||||
public final String getLocalName() { |
||||
return getName(locals); |
||||
} |
||||
|
||||
public String generateName(Identifier ident, String lastName) { |
||||
Collection coll = null; |
||||
if (ident instanceof PackageIdentifier) |
||||
coll = packs; |
||||
else if (ident instanceof ClassIdentifier) |
||||
coll = clazzes; |
||||
else if (ident instanceof MethodIdentifier) |
||||
coll = methods; |
||||
else if (ident instanceof FieldIdentifier) |
||||
coll = fields; |
||||
// else if (ident instanceof LocalIdentifier)
|
||||
// coll = locals;
|
||||
return getName(coll); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
/* Renamer Copyright (C) 1999 Jochen Hoenicke. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2, or (at your option) |
||||
* any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; see the file COPYING. If not, write to |
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
* |
||||
* $Id$ |
||||
*/ |
||||
|
||||
package jode.obfuscator; |
||||
|
||||
public interface Renamer { |
||||
|
||||
/** |
||||
* Generates a new name. |
||||
* @param ident the identifier that should be renamed. |
||||
* @param lastName the last name generated for this identifier, or |
||||
* null, if no name was generated yet. |
||||
*/ |
||||
public String generateName(Identifier ident, String lastName); |
||||
} |
@ -0,0 +1,63 @@ |
||||
/* StrongRenamer Copyright (C) 1999 Jochen Hoenicke. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2, or (at your option) |
||||
* any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; see the file COPYING. If not, write to |
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
||||
* |
||||
* $Id$ |
||||
*/ |
||||
|
||||
package jode.obfuscator; |
||||
|
||||
public class StrongRenamer implements Renamer { |
||||
String charSetStart; |
||||
String charSetCont; |
||||
|
||||
public StrongRenamer(String charSetStart, String charSetCont) { |
||||
this.charSetStart = charSetStart; |
||||
this.charSetCont = charSetCont; |
||||
} |
||||
|
||||
public String generateName(Identifier ident, String lastName) { |
||||
if (lastName == null) |
||||
return charSetStart.substring(0,1); |
||||
|
||||
char firstCont = charSetCont.charAt(0); |
||||
int pos = lastName.length() - 1; |
||||
StringBuffer sb = new StringBuffer(lastName.length() + 1); |
||||
while (pos > 0) { |
||||
int index = charSetCont.indexOf(lastName.charAt(pos)) + 1; |
||||
if (index < charSetCont.length()) { |
||||
sb.append(lastName.substring(0, pos)); |
||||
sb.append(charSetCont.charAt(index)); |
||||
for (int i = lastName.length() - pos - 1; i-- > 0; ) |
||||
sb.append(firstCont); |
||||
return sb.toString(); |
||||
} |
||||
pos --; |
||||
} |
||||
|
||||
int index = charSetStart.indexOf(lastName.charAt(pos)) + 1; |
||||
if (index < charSetStart.length()) { |
||||
sb.append(charSetStart.charAt(index)); |
||||
for (int i = lastName.length() - 1; i-- > 0; ) |
||||
sb.append(firstCont); |
||||
return sb.toString(); |
||||
} else { |
||||
sb.append(charSetStart.charAt(0)); |
||||
for (int i = lastName.length(); i-- > 0; ) |
||||
sb.append(firstCont); |
||||
} |
||||
return sb.toString(); |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
package jode.obfuscator; |
||||
|
||||
///#ifdef JDK12
|
||||
///import java.util.Map;
|
||||
///import java.util.TreeMap;
|
||||
///import java.util.Iterator;
|
||||
///#else
|
||||
import jode.util.Comparator; |
||||
import jode.util.Map; |
||||
import jode.util.TreeMap; |
||||
import jode.util.Iterator; |
||||
///#endif
|
||||
|
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
import java.io.BufferedReader; |
||||
import java.io.OutputStream; |
||||
import java.io.PrintWriter; |
||||
import java.io.IOException; |
||||
|
||||
public class TranslationTable extends TreeMap { |
||||
|
||||
///#ifndef JDK12
|
||||
public TranslationTable() { |
||||
super(createStringComparator()); |
||||
} |
||||
|
||||
private static Comparator createStringComparator() { |
||||
return new Comparator() { |
||||
public int compare(Object o1, Object o2) { |
||||
return ((String) o1).compareTo((String) o2); |
||||
} |
||||
}; |
||||
} |
||||
///#endif
|
||||
|
||||
public void load(InputStream in) throws IOException { |
||||
BufferedReader reader = |
||||
new BufferedReader(new InputStreamReader(in)); |
||||
|
||||
String line; |
||||
while ((line = reader.readLine()) != null) { |
||||
if (line.charAt(0) == '#') |
||||
continue; |
||||
int delim = line.indexOf('='); |
||||
String key = line.substring(0, delim); |
||||
String value = line.substring(delim+1); |
||||
put(key, value); |
||||
} |
||||
} |
||||
|
||||
public void store(OutputStream out) throws IOException { |
||||
PrintWriter writer = new PrintWriter(out); |
||||
for (Iterator i = entrySet().iterator(); i.hasNext(); ) { |
||||
Map.Entry e = (Map.Entry) i.next(); |
||||
writer.println(e.getKey()+"="+e.getValue()); |
||||
} |
||||
writer.flush(); |
||||
} |
||||
} |
@ -0,0 +1,198 @@ |
||||
// This class is taken from the Classpath project.
|
||||
// Please note the different copyright holder!
|
||||
// The changes I did is this comment, the package line, some
|
||||
// imports from java.util and some minor jdk12 -> jdk11 fixes.
|
||||
// -- Jochen Hoenicke <jochen@gnu.org>
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Bucket.java -- a class providing a hash-bucket data structure (a lightweight
|
||||
// linked list)
|
||||
//
|
||||
// Copyright (c) 1998 by Jon A. Zeppieri (jon@eease.com)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Library General Public License as published
|
||||
// by the Free Software Foundation, version 2. (see COPYING.LIB)
|
||||
//
|
||||
// This program 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 Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this program; if not, write to the Free Software Foundation
|
||||
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
package jode.util; |
||||
|
||||
/** |
||||
* a class representing a simple, lightweight linked-list, using Node |
||||
* objects as its linked nodes; this is used by Hashtable and HashMap |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
class Bucket |
||||
{ |
||||
/** the first node of the lined list, originally null */ |
||||
Node first; |
||||
|
||||
/** trivial constructor for a Bucket */ |
||||
Bucket() |
||||
{ |
||||
} |
||||
|
||||
/** add this key / value pair to the list |
||||
* |
||||
* @param entry a Map.Entry object to be added to this list |
||||
*/ |
||||
Map.Entry add(Node newNode) |
||||
{ |
||||
Object oKey; |
||||
Object oTestKey = newNode.getKey(); |
||||
Node it = first; |
||||
Node prev = null; |
||||
if (it == null) // if the list is empty (the ideal case), we make a new single-node list
|
||||
{ |
||||
first = newNode; |
||||
return null; |
||||
} |
||||
else // otherwise try to find where this key already exists in the list,
|
||||
{// and if it does, replace the value with the new one (and return the old one)
|
||||
while (it != null) |
||||
{ |
||||
oKey = it.getKey(); |
||||
if ((oKey == null) ? (oTestKey == null) : |
||||
oKey.equals(oTestKey)) |
||||
{ |
||||
if (prev != null) |
||||
prev.next = newNode; |
||||
else |
||||
first = newNode; |
||||
newNode.next = it.next; |
||||
return it; |
||||
} |
||||
prev = it; |
||||
it = it.next; |
||||
} |
||||
prev.next = newNode; // otherwise, just stick this at the
|
||||
return null; // end of the list
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* remove a Map.Entry in this list with the supplied key and return its value, |
||||
* if it exists, else return null |
||||
* |
||||
* @param key the key we are looking for in this list |
||||
*/ |
||||
Object removeByKey(Object key) |
||||
{ |
||||
Object oEntryKey; |
||||
Node prev = null; |
||||
Node it = first; |
||||
while (it != null) |
||||
{ |
||||
oEntryKey = it.getKey(); |
||||
if ((oEntryKey == null) ? (key == null) : oEntryKey.equals(key)) |
||||
{ |
||||
if (prev == null) // we are removing the first element
|
||||
first = it.next; |
||||
else |
||||
prev.next = it.next; |
||||
return it.getValue(); |
||||
} |
||||
else |
||||
{ |
||||
prev = it; |
||||
it = it.next; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* return the value which the supplied key maps to, if it maps to anything in this list, |
||||
* otherwise, return null |
||||
* |
||||
* @param key the key mapping to a value that we are looking for |
||||
*/ |
||||
Object getValueByKey(Object key) |
||||
{ |
||||
Node entry = getEntryByKey(key); |
||||
return (entry == null) ? null : entry.getValue(); |
||||
} |
||||
|
||||
/** |
||||
* return the Map.Entry which the supplied key is a part of, if such a Map.Entry exists, |
||||
* null otherwise |
||||
* |
||||
* this method is important for HashMap, which can hold null values and the null key |
||||
* |
||||
* @param key the key for which we are finding the corresponding Map.Entry |
||||
*/ |
||||
Node getEntryByKey(Object key) |
||||
{ |
||||
Object oEntryKey; |
||||
Node it = first; |
||||
while (it != null) |
||||
{ |
||||
oEntryKey = it.getKey(); |
||||
if ((oEntryKey == null) ? (key == null) : oEntryKey.equals(key)) |
||||
return it; |
||||
it = it.next; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* return true if this list has a Map.Entry whose value equals() the supplied value |
||||
* |
||||
* @param value the value we are looking to match in this list |
||||
*/ |
||||
boolean containsValue(Object value) |
||||
{ |
||||
Object oEntryValue; |
||||
Node it = first; |
||||
while (it != null) |
||||
{ |
||||
oEntryValue = it.getValue(); |
||||
if ((oEntryValue == null) ? (value == null) : oEntryValue.equals(value)) |
||||
return true; |
||||
it = it.next; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// INNSER CLASSES ----------------------------------------------------------
|
||||
|
||||
/** |
||||
* a class represnting a node in our lightweight linked-list |
||||
* that we use for hash buckets; a Node object contains a Map.Entry as its |
||||
* <pre>value</pre> property and a reference (possibly, even hopefully, null) |
||||
* to another Node as its <pre>next</pre> property. |
||||
* |
||||
* There <i>is</i> a reason for not using a highly generic "LinkedNode" type |
||||
* class: we want to eliminate runtime typechecks. |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
static class Node extends BasicMapEntry implements Map.Entry |
||||
{ |
||||
/** a reference to the next node in the linked list */ |
||||
Node next; |
||||
|
||||
/** non-trivial contructor -- sets the <pre>value</pre> of the Bucket upon instantiation */ |
||||
Node(Object key, Object value) |
||||
{ |
||||
super(key, value); |
||||
} |
||||
|
||||
|
||||
} |
||||
// EOF ------------------------------------------------------------------------
|
||||
} |
@ -0,0 +1,876 @@ |
||||
// This class is taken from the Classpath project.
|
||||
// Please note the different copyright holder!
|
||||
// The changes I did is this comment, the package line, some
|
||||
// imports from java.util and some minor jdk12 -> jdk11 fixes.
|
||||
// -- Jochen Hoenicke <jochen@gnu.org>
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// HashMap.java -- a class providing a basic hashtable data structure,
|
||||
// mapping Object --> Object; part of the JDK1.2 collections
|
||||
// API
|
||||
//
|
||||
// This is a JDK 1.2 compliant version of HashMap.java
|
||||
//
|
||||
// Copyright (c) 1998 by Jon A. Zeppieri (jon@eease.com),
|
||||
// Free Software Foundation, Inc.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Library General Public License as published
|
||||
// by the Free Software Foundation, version 2. (see COPYING.LIB)
|
||||
//
|
||||
// This program 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 Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this program; if not, write to the Free Software Foundation
|
||||
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
package jode.util; |
||||
import java.util.NoSuchElementException; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Serializable; |
||||
import java.io.ObjectInputStream; |
||||
import java.io.ObjectOutputStream; |
||||
|
||||
/** |
||||
* This class provides a hashtable-backed implementation of the |
||||
* Map interface. |
||||
* |
||||
* It uses a hash-bucket approach; that is, hash |
||||
* collisions are handled by linking the new node off of the |
||||
* pre-existing node (or list of nodes). In this manner, techniques |
||||
* such as linear probing (which can casue primary clustering) and |
||||
* rehashing (which does not fit very well with Java's method of |
||||
* precomputing hash codes) are avoided. |
||||
* |
||||
* Under ideal circumstances (no collisions, HashMap offers O(1) |
||||
* performance on most operations (<pre>containsValue()</pre> is, |
||||
* of course, O(n)). In the worst case (all keys map to the same |
||||
* hash code -- very unlikely), most operations are O(n). |
||||
* |
||||
* HashMap is part of the JDK1.2 Collections API. It differs from |
||||
* Hashtable in that it accepts the null key and null values, and it |
||||
* does not support "Enumeration views." |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
public class HashMap extends AbstractMap |
||||
implements Map, Cloneable, Serializable |
||||
{ |
||||
// STATIC (CLASS) VARIABLES ------------------------------------------
|
||||
|
||||
/** |
||||
* the default capacity for an instance of HashMap -- I think this |
||||
* is low, and perhaps it shoudl be raised; Sun's documentation mildly |
||||
* suggests that this (11) is the correct value, though |
||||
*/ |
||||
private static final int DEFAULT_CAPACITY = 11; |
||||
|
||||
/** the default load factor of a HashMap */ |
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75F; |
||||
|
||||
/** used internally to represent the null key */ |
||||
private static final HashMap.Null NULL_KEY = new HashMap.Null(); |
||||
|
||||
/** used internally to parameterize the creation of set/collection views */ |
||||
private static final int KEYS = 0; |
||||
|
||||
/** used internally to parameterize the creation of set/collection views */ |
||||
private static final int VALUES = 1; |
||||
|
||||
/** used internally to parameterize the creation of set/collection views */ |
||||
private static final int ENTRIES = 2; |
||||
|
||||
private static final long serialVersionUID = 362498820763181265L; |
||||
|
||||
// INSTANCE VARIABLES -------------------------------------------------
|
||||
|
||||
/** the capacity of this HashMap: denotes the size of the bucket array */ |
||||
transient int capacity; |
||||
|
||||
/** the size of this HashMap: denotes the number of key-value pairs */ |
||||
private transient int size; |
||||
|
||||
/** the load factor of this HashMap: used in computing the threshold |
||||
* @serial |
||||
*/ |
||||
float loadFactor; |
||||
|
||||
/* the rounded product of the capacity and the load factor; when the number of |
||||
* elements exceeds the threshold, the HashMap calls <pre>rehash()</pre> |
||||
* @serial |
||||
*/ |
||||
private int threshold; |
||||
|
||||
/** |
||||
* this data structure contains the actual key-value mappings; a |
||||
* <pre>BucketList</pre> is a lightweight linked list of "Buckets", |
||||
* which, in turn, are linked nodes containing a key-value mapping |
||||
* and a reference to the "next" Bucket in the list |
||||
*/ |
||||
private transient Bucket[] buckets; |
||||
|
||||
/** |
||||
* counts the number of modifications this HashMap has undergone; used by Iterators |
||||
* to know when to throw ConcurrentModificationExceptions (idea ripped-off from |
||||
* Stuart Ballard's AbstractList implementation) |
||||
*/ |
||||
private transient int modCount; |
||||
|
||||
|
||||
// CONSTRUCTORS ---------------------------------------------------------
|
||||
|
||||
/** |
||||
* construct a new HashMap with the default capacity and the default |
||||
* load factor |
||||
*/ |
||||
public HashMap() |
||||
{ |
||||
init(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); |
||||
} |
||||
|
||||
/** |
||||
* construct a new HashMap with a specific inital capacity and load factor |
||||
* |
||||
* @param initialCapacity the initial capacity of this HashMap (>=0) |
||||
* @param initialLoadFactor the load factor of this HashMap |
||||
* (a misnomer, really, since the load factor of |
||||
* a HashMap does not change) |
||||
* |
||||
* @throws IllegalArgumentException if (initialCapacity < 0) || |
||||
* (initialLoadFactor > 1.0) || |
||||
* (initialLoadFactor <= 0.0) |
||||
*/ |
||||
public HashMap(int initialCapacity, float initialLoadFactor) |
||||
throws IllegalArgumentException |
||||
{ |
||||
if (initialCapacity < 0 || initialLoadFactor <= 0 || initialLoadFactor > 1) |
||||
throw new IllegalArgumentException(); |
||||
else |
||||
init(initialCapacity, initialLoadFactor); |
||||
} |
||||
|
||||
/** |
||||
* construct a new HashMap with a specific inital capacity |
||||
* |
||||
* @param initialCapacity the initial capacity of this HashMap (>=0) |
||||
* |
||||
* @throws IllegalArgumentException if (initialCapacity < 0) |
||||
*/ |
||||
public HashMap(int initialCapacity) |
||||
throws IllegalArgumentException |
||||
{ |
||||
if (initialCapacity < 0) |
||||
throw new IllegalArgumentException(); |
||||
else |
||||
init(initialCapacity, DEFAULT_LOAD_FACTOR); |
||||
} |
||||
|
||||
/** |
||||
* construct a new HashMap from the given Map |
||||
* |
||||
* every element in Map t will be put into this new HashMap |
||||
* |
||||
* @param t a Map whose key / value pairs will be put into |
||||
* the new HashMap. <b>NOTE: key / value pairs |
||||
* are not cloned in this constructor</b> |
||||
*/ |
||||
public HashMap(Map t) |
||||
{ |
||||
int mapSize = t.size() * 2; |
||||
init(((mapSize > DEFAULT_CAPACITY) ? mapSize : DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR); |
||||
putAll(t); |
||||
} |
||||
|
||||
|
||||
// PUBLIC METHODS ---------------------------------------------------------
|
||||
|
||||
/** returns the number of kay-value mappings currently in this Map */ |
||||
public int size() |
||||
{ |
||||
return size; |
||||
} |
||||
|
||||
/** returns true if there are no key-value mappings currently in this Map */ |
||||
public boolean isEmpty() |
||||
{ |
||||
return size == 0; |
||||
} |
||||
|
||||
/** empties this HashMap of all elements */ |
||||
public void clear() |
||||
{ |
||||
size = 0; |
||||
modCount++; |
||||
buckets = new Bucket[capacity]; |
||||
} |
||||
|
||||
/** |
||||
* returns a shallow clone of this HashMap (i.e. the Map itself is cloned, but |
||||
* its contents are not) |
||||
*/ |
||||
public Object clone() |
||||
{ |
||||
Map.Entry entry; |
||||
Iterator it = entrySet().iterator(); |
||||
HashMap clone = new HashMap(capacity, loadFactor); |
||||
while (it.hasNext()) |
||||
{ |
||||
entry = (Map.Entry) it.next(); |
||||
clone.internalPut(entry.getKey(), entry.getValue()); |
||||
} |
||||
return clone; |
||||
} |
||||
|
||||
/** returns a "set view" of this HashMap's keys */ |
||||
public Set keySet() |
||||
{ |
||||
return new HashMapSet(KEYS); |
||||
} |
||||
|
||||
/** returns a "set view" of this HashMap's entries */ |
||||
public Set entrySet() |
||||
{ |
||||
return new HashMapSet(ENTRIES); |
||||
} |
||||
|
||||
/** returns a "collection view" (or "bag view") of this HashMap's values */ |
||||
public Collection values() |
||||
{ |
||||
return new HashMapCollection(); |
||||
} |
||||
|
||||
/** |
||||
* returns true if the supplied object equals (<pre>equals()</pre>) a key |
||||
* in this HashMap |
||||
* |
||||
* @param key the key to search for in this HashMap |
||||
*/ |
||||
public boolean containsKey(Object key) |
||||
{ |
||||
return (internalGet(key) != null); |
||||
} |
||||
|
||||
/** |
||||
* returns true if this HashMap contains a value <pre>o</pre>, such that |
||||
* <pre>o.equals(value)</pre>. |
||||
* |
||||
* @param value the value to search for in this Hashtable |
||||
*/ |
||||
public boolean containsValue(Object value) |
||||
{ |
||||
int i; |
||||
Bucket list; |
||||
|
||||
for (i = 0; i < capacity; i++) |
||||
{ |
||||
list = buckets[i]; |
||||
if (list != null && list.containsValue(value)) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/* |
||||
* return the value in this Hashtable associated with the supplied key, or <pre>null</pre> |
||||
* if the key maps to nothing |
||||
* |
||||
* @param key the key for which to fetch an associated value |
||||
*/ |
||||
public Object get(Object key) |
||||
{ |
||||
Map.Entry oResult = internalGet(key); |
||||
return (oResult == null) ? null : oResult.getValue(); |
||||
} |
||||
|
||||
/** |
||||
* puts the supplied value into the Map, mapped by the supplied key |
||||
* |
||||
* @param key the HashMap key used to locate the value |
||||
* @param value the value to be stored in the HashMap |
||||
*/ |
||||
public Object put(Object key, Object value) |
||||
{ |
||||
return internalPut(key, value); |
||||
} |
||||
|
||||
/** |
||||
* part of the Map interface; for each Map.Entry in t, the key / value pair is |
||||
* added to this Map, <b>using the <pre>put()</pre> method -- this may not be |
||||
* you want, so be warned (if you override the put() method, this could lead to |
||||
* off behavior)</b> |
||||
* |
||||
* @param t a Map whose key / value pairs will be added to this Hashtable |
||||
*/ |
||||
public void putAll(Map t) |
||||
{ |
||||
Map.Entry entry; |
||||
Iterator it = t.entrySet().iterator(); |
||||
while (it.hasNext()) |
||||
{ |
||||
entry = (Map.Entry) it.next(); |
||||
put(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* removes from the HashMap and returns the value which is mapped by the |
||||
* supplied key; if the key maps to nothing, then the HashMap remains unchanged, |
||||
* and <pre>null</pre> is returned |
||||
* |
||||
* @param key the key used to locate the value to remove from the HashMap |
||||
*/ |
||||
public Object remove(Object key) |
||||
{ |
||||
Bucket list; |
||||
int index; |
||||
Object result = null; |
||||
if (size > 0) |
||||
{ |
||||
index = hash(((key == null) ? NULL_KEY : key)); |
||||
list = buckets[index]; |
||||
if (list != null) |
||||
{ |
||||
result = list.removeByKey(key); |
||||
if (result != null) |
||||
{ |
||||
size--; |
||||
modCount++; |
||||
if (list.first == null) |
||||
buckets[index] = null; |
||||
} |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
// PRIVATE METHODS -----------------------------------------------------------
|
||||
|
||||
/** |
||||
* puts the given key-value pair into this HashMap; a private method is used |
||||
* because it is called by the rehash() method as well as the put() method, |
||||
* and if a subclass overrides put(), then rehash would do funky things |
||||
* if it called put() |
||||
* |
||||
* @param key the HashMap key used to locate the value |
||||
* @param value the value to be stored in the HashMap |
||||
*/ |
||||
private Object internalPut(Object key, Object value) |
||||
{ |
||||
HashMapEntry entry; |
||||
Bucket list; |
||||
int hashIndex; |
||||
Object oResult; |
||||
Object oRealKey = ((key == null) ? NULL_KEY : key); |
||||
|
||||
modCount++; |
||||
if (size == threshold) |
||||
rehash(); |
||||
entry = new HashMapEntry(oRealKey, value); |
||||
hashIndex = hash(oRealKey); |
||||
list = buckets[hashIndex]; |
||||
if (list == null) |
||||
{ |
||||
list = new Bucket(); |
||||
buckets[hashIndex] = list; |
||||
} |
||||
oResult = list.add(entry); |
||||
if (oResult == null) |
||||
{ |
||||
size++; |
||||
return null; |
||||
} |
||||
else |
||||
{ |
||||
return ((Map.Entry) oResult).getValue(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a private method, called by all of the constructors to initialize a new HashMap |
||||
* |
||||
* @param initialCapacity the initial capacity of this HashMap (>=0) |
||||
* @param initialLoadFactor the load factor of this HashMap |
||||
* (a misnomer, really, since the load factor of |
||||
* a HashMap does not change) |
||||
*/ |
||||
private void init(int initialCapacity, float initialLoadFactor) |
||||
{ |
||||
size = 0; |
||||
modCount = 0; |
||||
capacity = initialCapacity; |
||||
loadFactor = initialLoadFactor; |
||||
threshold = (int) ((float) capacity * loadFactor); |
||||
buckets = new Bucket[capacity]; |
||||
} |
||||
|
||||
/** private -- simply hashes a non-null Object to its array index */ |
||||
private int hash(Object key) |
||||
{ |
||||
return Math.abs(key.hashCode() % capacity); |
||||
} |
||||
|
||||
/** |
||||
* increases the size of the HashMap and rehashes all keys to new array indices; |
||||
* this is called when the addition of a new value would cause size() > threshold |
||||
*/ |
||||
private void rehash() |
||||
{ |
||||
int i; |
||||
Bucket[] data = buckets; |
||||
Bucket.Node node; |
||||
|
||||
modCount++; |
||||
capacity = (capacity * 2) + 1; |
||||
size = 0; |
||||
threshold = (int) ((float) capacity * loadFactor); |
||||
buckets = new Bucket[capacity]; |
||||
for (i = 0; i < data.length; i++) |
||||
{ |
||||
if (data[i] != null) |
||||
{ |
||||
node = data[i].first; |
||||
while (node != null) |
||||
{ |
||||
internalPut(node.getKey(), node.getValue()); |
||||
node = node.next; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a private method which does the "dirty work" (or some of it anyway) of fetching a value |
||||
* with a key |
||||
* |
||||
* @param key the key for which to fetch an associated value |
||||
*/ |
||||
private Map.Entry internalGet(Object key) |
||||
{ |
||||
Bucket list; |
||||
if (size == 0) |
||||
{ |
||||
return null; |
||||
} |
||||
else |
||||
{ |
||||
list = buckets[hash(((key == null) ? NULL_KEY : key))]; |
||||
return (list == null) ? null : list.getEntryByKey(key); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a private method used by inner class HashMapSet to implement its own |
||||
* <pre>contains(Map.Entry)</pre> method; returns true if the supplied |
||||
* key / value pair is found in this HashMap (again, using <pre>equals()</pre>, |
||||
* rather than <pre>==</pre>) |
||||
* |
||||
* @param entry a Map.Entry to match against key / value pairs in |
||||
* this HashMap |
||||
*/ |
||||
private boolean containsEntry(Map.Entry entry) |
||||
{ |
||||
Map.Entry oInternalEntry; |
||||
if (entry == null) |
||||
{ |
||||
return false; |
||||
} |
||||
else |
||||
{ |
||||
oInternalEntry = internalGet(entry.getKey()); |
||||
return (oInternalEntry != null && oInternalEntry.equals(entry)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Serializes this object to the given stream. |
||||
* @serialdata the <i>capacity</i>(int) that is the length of the |
||||
* bucket array, the <i>size</i>(int) of the hash map are emitted |
||||
* first. They are followed by size entries, each consisting of |
||||
* a key (Object) and a value (Object). |
||||
*/ |
||||
private void writeObject(ObjectOutputStream s) |
||||
throws IOException |
||||
{ |
||||
// the fields
|
||||
s.defaultWriteObject(); |
||||
|
||||
s.writeInt(capacity); |
||||
s.writeInt(size); |
||||
Iterator it = entrySet().iterator(); |
||||
while (it.hasNext()) |
||||
{ |
||||
Map.Entry oEntry = (Map.Entry) it.next(); |
||||
s.writeObject(oEntry.getKey()); |
||||
s.writeObject(oEntry.getValue()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Deserializes this object from the given stream. |
||||
* @serialdata the <i>capacity</i>(int) that is the length of the |
||||
* bucket array, the <i>size</i>(int) of the hash map are emitted |
||||
* first. They are followed by size entries, each consisting of |
||||
* a key (Object) and a value (Object). |
||||
*/ |
||||
private void readObject(ObjectInputStream s) |
||||
throws IOException, ClassNotFoundException |
||||
{ |
||||
// the fields
|
||||
s.defaultReadObject(); |
||||
|
||||
capacity = s.readInt(); |
||||
int iLen = s.readInt(); |
||||
size = 0; |
||||
modCount = 0; |
||||
buckets = new Bucket[capacity]; |
||||
|
||||
for (int i = 0; i < iLen; i++) |
||||
{ |
||||
Object oKey = s.readObject(); |
||||
Object oValue = s.readObject(); |
||||
internalPut(oKey, oValue); |
||||
} |
||||
} |
||||
|
||||
// INNER CLASSES -------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** |
||||
* an inner class providing a Set view of a HashMap; this implementation is |
||||
* parameterized to view either a Set of keys or a Set of Map.Entry objects |
||||
* |
||||
* Note: a lot of these methods are implemented by AbstractSet, and would work |
||||
* just fine without any meddling, but far greater efficiency can be gained by |
||||
* overriding a number of them. And so I did. |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
private class HashMapSet extends AbstractSet |
||||
implements Set |
||||
{ |
||||
/** the type of this Set view: KEYS or ENTRIES */ |
||||
private int setType; |
||||
|
||||
/** construct a new HashtableSet with the supplied view type */ |
||||
HashMapSet(int type) |
||||
{ |
||||
setType = type; |
||||
} |
||||
|
||||
/** |
||||
* adding an element is unsupported; this method simply throws an exception |
||||
* |
||||
* @throws UnsupportedOperationException |
||||
*/ |
||||
public boolean add(Object o) throws UnsupportedOperationException |
||||
{ |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
/** |
||||
* adding an element is unsupported; this method simply throws an exception |
||||
* |
||||
* @throws UnsupportedOperationException |
||||
*/ |
||||
public boolean addAll(Collection c) throws UnsupportedOperationException |
||||
{ |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
/** |
||||
* clears the backing HashMap; this is a prime example of an overridden implementation |
||||
* which is far more efficient than its superclass implementation (which uses an iterator |
||||
* and is O(n) -- this is an O(1) call) |
||||
*/ |
||||
public void clear() |
||||
{ |
||||
HashMap.this.clear(); |
||||
} |
||||
|
||||
/** |
||||
* returns true if the supplied object is contained by this Set |
||||
* |
||||
* @param o an Object being testing to see if it is in this Set |
||||
*/ |
||||
public boolean contains(Object o) |
||||
{ |
||||
if (setType == KEYS) |
||||
return HashMap.this.containsKey(o); |
||||
else |
||||
return (o instanceof Map.Entry) ? HashMap.this.containsEntry((Map.Entry) o) : false; |
||||
} |
||||
|
||||
/** |
||||
* returns true if the backing HashMap is empty (which is the only case either a KEYS |
||||
* Set or an ENTRIES Set would be empty) |
||||
*/ |
||||
public boolean isEmpty() |
||||
{ |
||||
return HashMap.this.isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* removes the supplied Object from the Set |
||||
* |
||||
* @param o the Object to be removed |
||||
*/ |
||||
public boolean remove(Object o) |
||||
{ |
||||
if (setType == KEYS) |
||||
return (HashMap.this.remove(o) != null); |
||||
else |
||||
return (o instanceof Map.Entry) ? |
||||
(HashMap.this.remove(((Map.Entry) o).getKey()) != null) : false; |
||||
} |
||||
|
||||
/** returns the size of this Set (always equal to the size of the backing Hashtable) */ |
||||
public int size() |
||||
{ |
||||
return HashMap.this.size(); |
||||
} |
||||
|
||||
/** returns an Iterator over the elements of this Set */ |
||||
public Iterator iterator() |
||||
{ |
||||
return new HashMapIterator(setType); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Like the above Set view, except this one if for values, which are not |
||||
* guaranteed to be unique in a Map; this prvides a Bag of values |
||||
* in the HashMap |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
private class HashMapCollection extends AbstractCollection |
||||
implements Collection |
||||
{ |
||||
/** a trivial contructor for HashMapCollection */ |
||||
HashMapCollection() |
||||
{ |
||||
} |
||||
|
||||
/** |
||||
* adding elements is not supported by this Collection; |
||||
* this method merely throws an exception |
||||
* |
||||
* @throws UnsupportedOperationException |
||||
*/ |
||||
public boolean add(Object o) throws UnsupportedOperationException |
||||
{ |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
/** |
||||
* adding elements is not supported by this Collection; |
||||
* this method merely throws an exception |
||||
* |
||||
* @throws UnsupportedOperationException |
||||
*/ |
||||
public boolean addAll(Collection c) throws UnsupportedOperationException |
||||
{ |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
/** removes all elements from this Collection (and from the backing HashMap) */ |
||||
public void clear() |
||||
{ |
||||
HashMap.this.clear(); |
||||
} |
||||
|
||||
/** |
||||
* returns true if this Collection contains at least one Object which equals() the |
||||
* supplied Object |
||||
* |
||||
* @param o the Object to compare against those in the Set |
||||
*/ |
||||
public boolean contains(Object o) |
||||
{ |
||||
return HashMap.this.containsValue(o); |
||||
} |
||||
|
||||
/** returns true IFF the Collection has no elements */ |
||||
public boolean isEmpty() |
||||
{ |
||||
return HashMap.this.isEmpty(); |
||||
} |
||||
|
||||
/** returns the size of this Collection */ |
||||
public int size() |
||||
{ |
||||
return HashMap.this.size(); |
||||
} |
||||
|
||||
/** returns an Iterator over the elements in this Collection */ |
||||
public Iterator iterator() |
||||
{ |
||||
return new HashMapIterator(VALUES); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a class which implements the Iterator interface and is used for |
||||
* iterating over HashMaps; |
||||
* this implementation is parameterized to give a sequential view of |
||||
* keys, values, or entries; it also allows the removal of elements, |
||||
* as per the Javasoft spec. |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
class HashMapIterator implements Iterator |
||||
{ |
||||
/** the type of this Iterator: KEYS, VALUES, or ENTRIES */ |
||||
private int myType; |
||||
/** |
||||
* the number of modifications to the backing Hashtable for which |
||||
* this Iterator can account (idea ripped off from Stuart Ballard) |
||||
*/ |
||||
private int knownMods; |
||||
/** the location of our sequential "cursor" */ |
||||
private int position; |
||||
/** the current index of the BucketList array */ |
||||
private int bucketIndex; |
||||
/** a reference, originally null, to the specific Bucket our "cursor" is pointing to */ |
||||
private Bucket.Node currentNode; |
||||
/** a reference to the current key -- used fro removing elements via the Iterator */ |
||||
private Object currentKey; |
||||
|
||||
/** construct a new HashtableIterator with the supllied type: KEYS, VALUES, or ENTRIES */ |
||||
HashMapIterator(int type) |
||||
{ |
||||
myType = type; |
||||
knownMods = HashMap.this.modCount; |
||||
position = 0; |
||||
bucketIndex = -1; |
||||
currentNode = null; |
||||
currentKey = null; |
||||
} |
||||
|
||||
/** |
||||
* Stuart Ballard's code: if the backing HashMap has been altered through anything |
||||
* but <i>this</i> Iterator's <pre>remove()</pre> method, we will give up right here, |
||||
* rather than risking undefined behavior |
||||
* |
||||
* @throws ConcurrentModificationException |
||||
*/ |
||||
private void checkMod() |
||||
{ |
||||
if (knownMods != HashMap.this.modCount) |
||||
throw new ConcurrentModificationException(); |
||||
} |
||||
|
||||
/** returns true if the Iterator has more elements */ |
||||
public boolean hasNext() |
||||
{ |
||||
checkMod(); |
||||
return position < HashMap.this.size(); |
||||
} |
||||
|
||||
/** returns the next element in the Iterator's sequential view */ |
||||
public Object next() |
||||
{ |
||||
Bucket list = null; |
||||
Object result; |
||||
checkMod(); |
||||
try |
||||
{ |
||||
while (currentNode == null) |
||||
{ |
||||
while (list == null) |
||||
list = HashMap.this.buckets[++bucketIndex]; |
||||
currentNode = list.first; |
||||
} |
||||
currentKey = currentNode.getKey(); |
||||
result = (myType == KEYS) ? currentKey : |
||||
((myType == VALUES) ? currentNode.getValue() : currentNode); |
||||
currentNode = currentNode.next; |
||||
} |
||||
catch(Exception e) |
||||
{ |
||||
throw new NoSuchElementException(); |
||||
} |
||||
position++; |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* removes from the backing HashMap the last element which was fetched with the |
||||
* <pre>next()</pre> method |
||||
*/ |
||||
public void remove() |
||||
{ |
||||
checkMod(); |
||||
if (currentKey == null) |
||||
{ |
||||
throw new IllegalStateException(); |
||||
} |
||||
else |
||||
{ |
||||
HashMap.this.remove(currentKey); |
||||
knownMods++; |
||||
currentKey = null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a singleton instance of this class (HashMap.NULL_KEY) |
||||
* is used to represent the null key in HashMap objects |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
private static class Null |
||||
{ |
||||
/** trivial constructor */ |
||||
Null() |
||||
{ |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* a HashMap version of Map.Entry -- one thing in this implementation is |
||||
* HashMap-specific: if the key is HashMap.NULL_KEY, getKey() will return |
||||
* null |
||||
* |
||||
* Simply, a key / value pair |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
private static class HashMapEntry extends Bucket.Node implements Map.Entry |
||||
{ |
||||
/** construct a new HashMapEntry with the given key and value */ |
||||
public HashMapEntry(Object key, Object value) |
||||
{ |
||||
super(key, value); |
||||
} |
||||
|
||||
/** |
||||
* if the key == HashMap.NULL_KEY, null is returned, otherwise the actual |
||||
* key is returned |
||||
*/ |
||||
public Object getKey() |
||||
{ |
||||
Object oResult = super.getKey(); |
||||
return (oResult == HashMap.NULL_KEY) ? null : oResult; |
||||
} |
||||
} |
||||
// EOF -----------------------------------------------------------------------
|
||||
} |
@ -0,0 +1,238 @@ |
||||
// This class is taken from the Classpath project.
|
||||
// Please note the different copyright holder!
|
||||
// The changes I did is this comment, the package line, some
|
||||
// imports from java.util and some minor jdk12 -> jdk11 fixes.
|
||||
// -- Jochen Hoenicke <jochen@gnu.org>
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// HashSet.java -- a class providing a HashMap-backet Set
|
||||
//
|
||||
// Copyright (c) 1998 by Jon A. Zeppieri (jon@eease.com)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Library General Public License as published
|
||||
// by the Free Software Foundation, version 2. (see COPYING.LIB)
|
||||
//
|
||||
// This program 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 Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this program; if not, write to the Free Software Foundation
|
||||
// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
package jode.util; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Serializable; |
||||
import java.io.ObjectInputStream; |
||||
import java.io.ObjectOutputStream; |
||||
|
||||
/** |
||||
* This class provides a HashMap-backed implementation of the |
||||
* Set interface. |
||||
* |
||||
* Each element in the Set is a key in the backing HashMap; each key |
||||
* maps to a static token, denoting that the key does, in fact, exist. |
||||
* |
||||
* Most operations are O(1), assuming no hash collisions. In the worst |
||||
* case (where all hases collide), operations are O(n). |
||||
* |
||||
* HashSet is a part of the JDK1.2 Collections API. |
||||
* |
||||
* @author Jon Zeppieri |
||||
* @version $Revision$ |
||||
* @modified $Id$ |
||||
*/ |
||||
public class HashSet extends AbstractSet |
||||
implements Set, Cloneable, Serializable |
||||
{ |
||||
// INSTANCE VARIABLES -------------------------------------------------
|
||||
/** the HashMap which backs this Set */ |
||||
private transient HashMap map; |
||||
static final long serialVersionUID = -5024744406713321676L; |
||||
|
||||
// CONSTRUCTORS ---------------------------------------------------------
|
||||
|
||||
/** |
||||
* construct a new, empty HashSet whose backing HashMap has the default |
||||
* capacity and loadFacor |
||||
*/ |
||||
public HashSet() |
||||
{ |
||||
map = new HashMap(); |
||||
} |
||||
|
||||
/** |
||||
* construct a new, empty HashSet whose backing HashMap has the supplied |
||||
* capacity and the default load factor |
||||
* |
||||
* @param initialCapacity the initial capacity of the backing |
||||
* HashMap |
||||
*/ |
||||
public HashSet(int initialCapacity) |
||||
{ |
||||
map = new HashMap(initialCapacity); |
||||
} |
||||
|
||||
/** |
||||
* construct a new, empty HashSet whose backing HashMap has the supplied |
||||
* capacity and load factor |
||||
* |
||||
* @param initialCapacity the initial capacity of the backing |
||||
* HashMap |
||||
* @param loadFactor the load factor of the backing HashMap |
||||
*/ |
||||
public HashSet(int initialCapacity, float loadFactor) |
||||
{ |
||||
map = new HashMap(initialCapacity, loadFactor); |
||||
} |
||||
|
||||
/** |
||||
* construct a new HashSet with the same elements as are in the supplied |
||||
* collection (eliminating any duplicates, of course; the backing HashMap |
||||
* will have the default capacity and load factor |
||||
* |
||||
* @param c a collection containing the elements with |
||||
* which this set will be initialized |
||||
*/ |
||||
public HashSet(Collection c) |
||||
{ |
||||
map = new HashMap(); |
||||
addAll(c); |
||||
} |
||||
|
||||
|
||||
// PUBLIC METHODS ---------------------------------------------------------
|
||||
|
||||
/** |
||||
* adds the given Object to the set if it is not already in the Set, |
||||
* returns true if teh element was added, false otherwise |
||||
* |
||||
* @param o the Object to add to this Set |
||||
*/ |
||||
public boolean add(Object o) |
||||
{ |
||||
if (map.containsKey(o)) |
||||
{ |
||||
return false; |
||||
} |
||||
else |
||||
{ |
||||
internalAdd(o); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* empties this Set of all elements; this is a fast operation [O(1)] |
||||
*/ |
||||
public void clear() |
||||
{ |
||||
map.clear(); |
||||
} |
||||
|
||||
/** |
||||
* returns a shallow copy of this Set (the Set itself is cloned; its |
||||
* elements are not) |
||||
*/ |
||||
public Object clone() |
||||
{ |
||||
Iterator it = iterator(); |
||||
HashSet clone = new HashSet(map.capacity, map.loadFactor); |
||||
while (it.hasNext()) |
||||
clone.internalAdd(it.next()); |
||||
return clone; |
||||
} |
||||
|
||||
/** |
||||
* returns true if the supplied element is in this Set, false otherwise |
||||
* |
||||
* @param o the Object whose presence in this Set we are testing for |
||||
*/ |
||||
public boolean contains(Object o) |
||||
{ |
||||
return map.containsKey(o); |
||||
} |
||||
|
||||
/** |
||||
* returns true if this set has no elements in it (size() == 0) |
||||
*/ |
||||
public boolean isEmpty() |
||||
{ |
||||
return map.isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* returns an Iterator over the elements of this Set; the Iterator allows |
||||
* removal of elements |
||||
*/ |
||||
public Iterator iterator() |
||||
{ |
||||
return map.keySet().iterator(); |
||||
} |
||||
|
||||
/** |
||||
* removes the supplied Object from this Set if it is in the Set; returns |
||||
* true if an element was removed, false otherwise |
||||
*/ |
||||
public boolean remove(Object o) |
||||
{ |
||||
return (map.remove(o) != null); |
||||
} |
||||
|
||||
/** |
||||
* returns the number of elements in this Set |
||||
*/ |
||||
public int size() |
||||
{ |
||||
return map.size(); |
||||
} |
||||
|
||||
|
||||
// PRIVATE METHODS -----------------------------------------------------------
|
||||
|
||||
/** |
||||
* adds the supplied element to this Set; this private method is used |
||||
* internally [clone()] instead of add(), because add() can be overridden |
||||
* to do unexpected things |
||||
* |
||||
* @param o the Object to add to this Set |
||||
*/ |
||||
private void internalAdd(Object o) |
||||
{ |
||||
map.put(o, Boolean.TRUE); |
||||
} |
||||
|
||||
/** Serialize this Object in a manner which is binary-compatible with the JDK */ |
||||
private void writeObject(ObjectOutputStream s) throws IOException |
||||
{ |
||||
Iterator it = iterator(); |
||||
s.writeInt(map.capacity); |
||||
s.writeFloat(map.loadFactor); |
||||
s.writeInt(size()); |
||||
while (it.hasNext()) |
||||
s.writeObject(it.next()); |
||||
} |
||||
|
||||
/** Deserialize this Object in a manner which is binary-compatible with the JDK */ |
||||
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException |
||||
{ |
||||
int i, iSize, iCapacity; |
||||
float fLoadFactor; |
||||
Object oElement; |
||||
|
||||
iCapacity = s.readInt(); |
||||
fLoadFactor = s.readFloat(); |
||||
iSize = s.readInt(); |
||||
|
||||
map = new HashMap(iCapacity, fLoadFactor); |
||||
|
||||
for (i = 0; i < iSize; i++) |
||||
{ |
||||
oElement = s.readObject(); |
||||
internalAdd(oElement); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
.class public jode/test/Base |
||||
.super java/lang/Object |
||||
|
||||
.field public static test I |
||||
.field public test J |
||||
|
||||
.method <init>()V |
||||
aload_0 |
||||
invokespecial java/lang/Object/<init>()V |
||||
return |
||||
.end method |
||||
|
@ -0,0 +1,29 @@ |
||||
.class public jode/test/Child |
||||
.super jode/test/Base |
||||
|
||||
.field private test I |
||||
|
||||
.method <init>()V |
||||
.limit locals 1 |
||||
.limit stack 2 |
||||
aload_0 |
||||
invokespecial jode/test/Base/<init>()V |
||||
getstatic jode/test/Base/test I |
||||
pop |
||||
aload_0 |
||||
getfield jode/test/Base/test J |
||||
pop2 |
||||
aload_0 |
||||
getfield jode/test/Child/test I |
||||
pop |
||||
aload_0 |
||||
getfield jode/test/Child/test J |
||||
pop2 |
||||
return |
||||
.end method |
||||
|
||||
.method public static main([Ljava/lang/String;)V |
||||
.limit locals 1 |
||||
.limit stack 0 |
||||
return |
||||
.end method |
Loading…
Reference in new issue