How to design a model to allow apply or cancel updates? - java

Is there a design or development pattern where we deal with making updates to a copy of the actual data and applying the diff to the original reference if needed?
If not, what is the best way of designing such models?
What I think I should do:
I should probably use an enum mode to indicate whether the model is being used in 'Update direct reference mode' OR 'Update only a copy mode'
Update the setters and getters of data to reference the actualState or the temporaryState as per what mode is the model being used in.
Have the setter method for mode to create a copy of the actual data and store it in a temporary state. If the mode is updated to update direct reference, clear out the temporaryState
Create a method for applying the changes from temporaryState to the actualState. This method shall also clear out the temporary state from memory.
In code:
enum InsertionMode {
UPDATE_DIRECT, UPDATE_COPY
}
class Store {
private Data actualState;
private Data temporaryState;
private InsertionMode mode;
private void resetTemporaryState() {
....
}
private void initTemporaryState() {
this.temporaryState = copy(actualState);
}
private commitTemporaryState() {
this.actualState = this.temporaryState;
this.resetTemporaryState();
}
public Data setInsertionMode(InsertionMode mode) {
if (this.mode != mode) {
InsertionMode previousMode = this.mode;
this.mode = mode;
if (previousMode == InsertionMode.UPDATE_COPY) {
this.resetTemporaryState();
}
if (this.mode == InsertionMode.UPDATE_COPY) {
this.initTemporaryState();
}
}
}
public void commit() {
if (this.mode == InsertionMode.UPDATE_COPY) {
this.commitTemporaryState();
}
}
public void abort() {
if (this.mode == InsertionMode.UPDATE_COPY) {
this.resetTemporaryState();
this.setInsertionMode(InsertionMode.UPDATE_DIRECT);
}
}
...
}

The given code is "okay", as it will support your requirements.
But: updating objects is a simple approach, and is easy to implement. But depending on your context, you do things really differently in 2017.
Instead of having one mutable object that changes state, you could instead go for immutable objects. State becomes a sequence of such objects.
Reaching a new state means adding a newly created object at the end of the sequence, cancel means to go with the old, unchanged sequence. This approach is the base for blockchain applications; but it can be scaled down to a smaller context as well - just by looking at its core aspect: you never change state by changing existing objects, but by creating new objects. Of course, this needs a lot of thought; you don't want to blindly duplicate everything; you might more be looking having "delta" objects (that represent individual changes) and "views" that show aggregations of deltas.
Beyond that: you might want to read about CQRS versus CRUD (for example this).

Related

Java class: limit instance variable to one of several possible values, depending on other instance variables

I am sorry for the vague question. I am not sure what I'm looking for here.
I have a Java class, let's call it Bar. In that class is an instance variable, let's call it foo. foo is a String.
foo cannot just have any value. There is a long list of strings, and foo must be one of them.
Then, for each of those strings in the list I would like the possibility to set some extra conditions as to whether that specific foo can belong in that specific type of Bar (depending on other instance variables in that same Bar).
What approach should I take here? Obviously, I could put the list of strings in a static class somewhere and upon calling setFoo(String s) check whether s is in that list. But that would not allow me to check for extra conditions - or I would need to put all that logic for every value of foo in the same method, which would get ugly quickly.
Is the solution to make several hundred classes for every possible value of foo and insert in each the respective (often trivial) logic to determine what types of Bar it fits? That doesn't sound right either.
What approach should I take here?
Here's a more concrete example, to make it more clear what I am looking for. Say there is a Furniture class, with a variable material, which can be lots of things, anything from mahogany to plywood. But there is another variable, upholstery, and you can make furniture containing cotton of plywood but not oak; satin furniture of oak but not walnut; other types of fabric go well with any material; et cetera.
I wouldn't suggest creating multiple classes/templates for such a big use case. This is very opinion based but I'll take a shot at answering as best as I can.
In such a case where your options can be numerous and you want to keep a maintainable code base, the best solution is to separate the values and the logic. I recommend that you store your foo values in a database. At the same time, keep your client code as clean and small as possible. So that it doesn't need to filter through the data to figure out which data is valid. You want to minimize dependency to data in your code. Think of it this way: tomorrow you might need to add a new material to your material list. Do you want to modify all your code for that? Or do you want to just add it to your database and everything magically works? Obviously the latter is a better option. Here is an example on how to design such a system. Of course, this can vary based on your use case or variables but it is a good guideline. The basic rule of thumb is: your code should have as little dependency to data as possible.
Let's say you want to create a Bar which has to have a certain foo. In this case, I would create a database for BARS which contains all the possible Bars. Example:
ID NAME FOO
1 Door 1,4,10
I will also create a database FOOS which contains the details of each foo. For example:
ID NAME PROPERTY1 PROPERTY2 ...
1 Oak Brown Soft
When you create a Bar:
Bar door = new Bar(Bar.DOOR);
in the constructor you would go to the BARS table and query the foos. Then you would query the FOOS table and load all the material and assign them to the field inside your new object.
This way whenever you create a Bar the material can be changed and loaded from DB without changing any code. You can add as many types of Bar as you can and change material properties as you goo. Your client code however doesn't change much.
You might ask why do we create a database for FOOS and refer to it's ids in the BARS table? This way, you can modify the properties of each foo as much as you want. Also you can share foos between Bars and vice versa but you only need to change the db once. cross referencing becomes a breeze. I hope this example explains the idea clearly.
You say:
Is the solution to make several hundred classes for every possible
value of foo and insert in each the respective (often trivial) logic
to determine what types of Bar it fits? That doesn't sound right
either.
Why not have separate classes for each type of Foo? Unless you need to define new types of Foo without changing the code you can model them as plain Java classes. You can go with enums as well but it does not really give you any advantage since you still need to update the enum when adding a new type of Foo.
In any case here is type safe approach that guarantees compile time checking of your rules:
public static interface Material{}
public static interface Upholstery{}
public static class Oak implements Material{}
public static class Plywood implements Material{}
public static class Cotton implements Upholstery{}
public static class Satin implements Upholstery{}
public static class Furniture<M extends Material, U extends Upholstery>{
private M matrerial = null;
private U upholstery = null;
public Furniture(M matrerial, U upholstery){
this.matrerial = matrerial;
this.upholstery = upholstery;
}
public M getMatrerial() {
return matrerial;
}
public U getUpholstery() {
return upholstery;
}
}
public static Furniture<Plywood, Cotton> cottonFurnitureWithPlywood(Plywood plywood, Cotton cotton){
return new Furniture<>(plywood, cotton);
}
public static Furniture<Oak, Satin> satinFurnitureWithOak(Oak oak, Satin satin){
return new Furniture<>(oak, satin);
}
It depends on what you really want to achieve. Creating objects and passing them around will not magically solve your domain-specific problems.
If you cannot think of any real behavior to add to your objects (except the validation), then it might make more sense to just store your data and read them into memory whenever you want. Even treat rules as data.
Here is an example:
public class Furniture {
String name;
Material material;
Upholstery upholstery;
//getters, setters, other behavior
public Furniture(String name, Material m, Upholstery u) {
//Read rule files from memory or disk and do all the checks
//Do not instantiate if validation does not pass
this.name = name;
material = m;
upholstery = u;
}
}
To specify rules, you will then create three plain text files (e.g. using csv format). File 1 will contain valid values for material, file 2 will contain valid values for upholstery, and file 3 will have a matrix format like the following:
upholstery\material plywood mahogany oak
cotton 1 0 1
satin 0 1 0
to check if a material goes with an upholstery or not, just check the corresponding row and column.
Alternatively, if you have lots of data, you can opt for a database system along with an ORM. Rule tables then can be join tables and come with extra nice features a DBMS may provide (like easy checking for duplicate values). The validation table could look something like:
MaterialID UpholsteryID Compatability_Score
plywood cotton 1
oak satin 0
The advantage of using this approach is that you quickly get a working application and you can decide what to do as you add new behavior to your application. And even if it gets way more complex in the future (new rules, new data types, etc) you can use something like the repository pattern to keep your data and business logic decoupled.
Notes about Enums:
Although the solution suggested by #Igwe Kalu solves the specific case described in the question, it is not scalable. What if you want to find what material goes with a given upholstery (the reverse case)? You will need to create another enum which does not add anything meaningful to the program, or add complex logic to your application.
This is a more detailed description of the idea I threw out there in the comment:
Keep Furniture a POJO, i.e., just hold the data, no behavior or rules implemented in it.
Implement the rules in separate classes, something along the lines of:
interface FurnitureRule {
void validate(Furniture furniture) throws FurnitureRuleException;
}
class ValidMaterialRule implements FurnitureRule {
// this you can load in whatever way suitable in your architecture -
// from enums, DB, an XML file, a JSON file, or inject via Spring, etc.
private Set<String> validMaterialNames;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
if (!validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial());
}
}
class UpholsteryRule implements FurnitureRule {
// Again however suitable to implement/config this
private Map<String, Set<String>> validMaterialsPerUpholstery;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
Set<String> validMaterialNames = validMaterialsPerUpholstery.get(furniture.getUpholstery();
if (validMaterialNames != null && !validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial() + " for upholstery " + furniture.getUpholstery());
}
}
// and more complex rules if you need to
Then have some service along the lines of FurnitureManager. It's the "gatekeeper" for all Furniture creation/updates:
class FurnitureManager {
// configure these via e.g. Spring.
private List<FurnitureRule> rules;
public void updateFurniture(Furniture furniture) throws FurnitureRuleException {
rules.forEach(rule -> rule.validate(furniture))
// proceed to persist `furniture` in the database or whatever else you do with a valid piece of furniture.
}
}
material should be of type Enum.
public enum Material {
MAHOGANY,
TEAK,
OAK,
...
}
Furthermore you can have a validator for Furniture that contains the logic which types of Furniture make sense, and then call that validator in every method that can change the material or upholstery variable (typically only your setters).
public class Furniture {
private Material material;
private Upholstery upholstery; //Could also be String depending on your needs of course
public void setMaterial(Material material) {
if (FurnitureValidator.isValidCombination(material, this.upholstery)) {
this.material = material;
}
}
...
private static class FurnitureValidator {
private static boolean isValidCombination(Material material, Upholstery upholstery) {
switch(material) {
case MAHOGANY: return upholstery != Upholstery.COTTON;
break;
//and so on
}
}
}
}
We often are oblivious of the power inherent in enum types. The Java™ Tutorials clearly states "you should use enum types any time you need to represent a fixed set of constants."
How do you simply make the best of enum in resolving the challenge you presented? - Here goes:
public enum Material {
MAHOGANY( "satin", "velvet" ),
PLYWOOD( "leather" ),
// possibly many other materials and their matching fabrics...
OAK( "some other fabric - 0" ),
WALNUT( "some other fabric - 0", "some other fabric - 1" );
private final String[] listOfSuitingFabrics;
Material( String... fabrics ) {
this.listOfSuitingFabrics = fabrics;
}
String[] getListOfSuitingFabrics() {
return Arrays.copyOf( listOfSuitingFabrics );
}
public String toString() {
return name().substring( 0, 1 ) + name().substring( 1 );
}
}
Let's test it:
public class TestMaterial {
for ( Material material : Material.values() ) {
System.out.println( material.toString() + " go well with " + material.getListOfSuitingFabrics() );
}
}
Probably the approach I'd use (because it involves the least amount of code and it's reasonably fast) is to "flatten" the hierarchical logic into a one-dimensional Set of allowed value combinations. Then when setting one of the fields, validate that the proposed new combination is valid. I'd probably just use a Set of concatenated Strings for simplicity. For the example you give above, something like this:
class Furniture {
private String wood;
private String upholstery;
/**
* Set of all acceptable values, with each combination as a String.
* Example value: "plywood:cotton"
*/
private static final Set<String> allowed = new HashSet<>();
/**
* Load allowed values in initializer.
*
* TODO: load allowed values from DB or config file
* instead of hard-wiring.
*/
static {
allowed.add("plywood:cotton");
...
}
public void setWood(String wood) {
if (!allowed.contains(wood + ":" + this.upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
}
public void setUpholstery(String upholstery) {
if (!allowed.contains(this.wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.upholstery = upholstery;
}
public void setMaterials(String wood, String upholstery) {
if (!allowed.contains(wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
this.upholstery = upholstery;
}
// getters
...
}
The disadvantage of this approach compared to other answers is that there is no compile-time type checking. For example, if you try to set the wood to plywoo instead of plywood you won’t know about your error until runtime. In practice this disadvantage is negligible since presumably the options will be chosen by a user through a UI (or through some other means), so you won’t know what they are until runtime anyway. Plus the big advantage is that the code will never have to be changed so long as you’re willing to maintain a list of allowed combinations externally. As someone with 30 years of development experience, take my word for it that this approach is far more maintainable.
With the above code, you'll need to use setMaterials before using setWood or setUpholstery, since the other field will still be null and therefore not an allowed combination. You can initialize the class's fields with default materials to avoid this if you want.

reading a reference to an object and reading the object’s fields under JMM

This post was raised after reading: https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/#pitfall-semi-sync
class Box {
int x;
public Box(int v) {
x = v;
}
}
class RacyBoxy {
Box box;
public synchronized void set(Box v) {
box = v;
}
public Box get() {
return box;
}
}
and test:
#JCStressTest
#State
public class SynchronizedPublish {
RacyBoxy boxie = new RacyBoxy();
#Actor
void actor() {
boxie.set(new Box(42)); // set is synchronized
}
#Actor
void observer(IntResult1 r) {
Box t = boxie.get(); // get is not synchronized
if (t != null) {
r.r1 = t.x;
} else {
r.r1 = -1;
}
}
}
The author says that it is possible that r.r1 == 0. And I agree with
that. But, I am confused with an explanation:
The actual failure comes from the fact that reading a reference to an object and reading the object’s fields are distinct under the memory model.
I agree that
reading a reference to an object and reading the object’s fields are distinct under the memory model
but, I don't see how it has an influence on result.
Please help me understand it.
P.S. If someone is confused about #Actor. It just means: run in a thread.
I think it adresses a common miconception of people that read code with regards to sequential consitency. The fact that the reference to an instance is available in one thread, does not imply that its constructor is set. In other words: reading an instance is a different operation than reading an instance's field. Many people assume that once they can observe an instance, it requires the constructor to be run but due to the missing read synchronization, this is not true for the above example.
Ill just slightly augment the accepted answer here - without some barriers there are absolutely no guarantees that once you see a reference (think some threads can get a hold of a reference) - all the fields from that constructor are initialized. I actually answered sort of this already some time ago to one of your questions if I'm not mistaken.
There are two barriers inserted after the constructor that has final fields LoadLoad and LoadStore; it you think about their names - you will notice that no operation after the constructor can be re-ordered with one inside it:
Load -> Load (no Load can be re-ordered with a previous Load)
Load -> Store (no Store can be re-ordered with a previous Load)
Also note that it would be impossible for you to break that under the current x86 memory model - as it is a (too?) strong memory model; and as such these barriers are free on x86 - they are not inserted at all, because the operations are not re-ordered.

Security - Array is stored directly - String [][] [duplicate]

There is a Sonar Violation:
Sonar Violation: Security - Array is stored directly
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
Solution:
public void setMyArray(String[] newMyArray) {
if(newMyArray == null) {
this.myArray = new String[0];
} else {
this.myArray = Arrays.copyOf(newMyArray, newMyArray.length);
}
}
But I wonder why ?
It's complaining that the array you're storing is the same array that is held by the caller. That is, if the caller subsequently modifies this array, the array stored in the object (and hence the object itself) will change.
The solution is to make a copy within the object when it gets passed. This is called defensive copying. A subsequent modification of the collection won't affect the array stored within the object.
It's also good practice to normally do this when returning a collection (e.g. in a corresponding getMyArray() call). Otherwise the receiver could perform a modification and affect the stored instance.
Note that this obviously applies to all mutable collections (and in fact all mutable objects) - not just arrays. Note also that this has a performance impact which needs to be assessed alongside other concerns.
It's called defensive copying. A nice article on the topic is "Whose object is it, anyway?" by Brian Goetz, which discusses difference between value and reference semantics for getters and setters.
Basically, the risk with reference semantics (without a copy) is that you erronously think you own the array, and when you modify it, you also modify other structures that have aliases to the array. You can find many information about defensive copying and problems related to object aliasing online.
I had the same issue:
Security - Array is stored directly The user-supplied array
'palomitas' is stored directly.
my original method:
public void setCheck(boolean[] palomitas) {
this.check=palomitas;
}
fixed turned to:
public void setCheck(boolean[] palomitas) {
if(palomitas == null) {
this.check = new boolean[0];
} else {
this.check = Arrays.copyOf(palomitas, palomitas.length);
}
}
Other Example:
Security - Array is stored directly The user-supplied array
private String[] arrString;
public ListaJorgeAdapter(String[] stringArg) {
arrString = stringArg;
}
Fixed:
public ListaJorgeAdapter(String[] stringArg) {
if(stringArg == null) {
this.arrString = new String[0];
} else {
this.arrString = Arrays.copyOf(stringArg, stringArg.length);
}
}
To eliminate them you have to clone the Array before storing / returning it as shown in the following class implementation, so noone can modify or get the original data of your class but only a copy of them.
public byte[] getarrString() {
return arrString.clone();
}
/**
* #param arrStringthe arrString to set
*/
public void arrString(byte[] arrString) {
this.arrString= arrString.clone();
}
I used it like this and Now I am not getting any SONAR violation...
It's more ease than all of this. You only need to rename the method parameter to anything else to avoid Sonar violations.
http://osdir.com/ml/java-sonar-general/2012-01/msg00223.html
public void setInventoryClassId(String[] newInventoryClassId)
{
if(newInventoryClassId == null)
{
this.inventoryClassId = new String[0];
}
else
{
this.inventoryClassId = Arrays.copyOf(newInventoryClassId, newInventoryClassId.length);
}
}
To go the defensive-implementation-way can save you a lot of time.
In Guava you get another nice solution to reach the goal: ImmutableCollections
http://code.google.com/p/guava-libraries/wiki/ImmutableCollectionsExplained
There are certain cases where it is a design decision and not missed out. In these cases, you need to modify the Sonar rules to exclude it so that it doesn't show such issues in report.

Java Server Client, shared variable between threads

I am working on a project to create a simple auction server that multiple clients connect to. The server class implements Runnable and so creates a new thread for each client that connects.
I am trying to have the current highest bid stored in a variable that can be seen by each client. I found answers saying to use AtomicInteger, but when I used it with methods such as atomicVariable.intValue() I got null pointer exception errors.
What ways can I manipulate the AtomicInteger without getting this error or is there an other way to have a shared variable that is relatively simple?
Any help would be appreciated, thanks.
Update
I have the AtomicInteger working. The problem is now that only the most recent client to connect to the server seems to be able to interact with it. The other client just sort of freeze.
Would I be correct in saying this is a problem with locking?
Well, most likely you forgot to initialize it:
private final AtomicInteger highestBid = new AtomicInteger();
However working with highestBid requires a great deal of knowledge to get it right without any locking. For example if you want to update it with new highest bid:
public boolean saveIfHighest(int bid) {
int currentBid = highestBid.get();
while (currentBid < bid) {
if (highestBid.compareAndSet(currentBid, bid)) {
return true;
}
currentBid = highestBid.get();
}
return false;
}
or in a more compact way:
for(int currentBid = highestBid.get(); currentBid < bid; currentBid = highestBid.get()) {
if (highestBid.compareAndSet(currentBid, bid)) {
return true;
}
}
return false;
You might wonder, why is it so hard? Image two threads (requests) biding at the same time. Current highest bid is 10. One is biding 11, another 12. Both threads compare current highestBid and realize they are bigger. Now the second thread happens to be first and update it to 12. Unfortunately the first request now steps in and revert it to 11 (because it already checked the condition).
This is a typical race condition that you can avoid either by explicit synchronization or by using atomic variables with implicit compare-and-set low-level support.
Seeing the complexity introduced by much more performant lock-free atomic integer you might want to restore to classic synchronization:
public synchronized boolean saveIfHighest(int bid) {
if (highestBid < bid) {
highestBid = bid;
return true;
} else {
return false;
}
}
I wouldn't look at the problem like that. I would simply store all the bids in a ConcurrentSkipListSet, which is a thread-safe SortedSet. With the correct implementation of compareTo(), which determines the ordering, the first element of the Set will automatically be the highest bid.
Here's some sample code:
public class Bid implements Comparable<Bid> {
String user;
int amountInCents;
Date created;
#Override
public int compareTo(Bid o) {
if (amountInCents == o.amountInCents) {
return created.compareTo(created); // earlier bids sort first
}
return o.amountInCents - amountInCents; // larger bids sort first
}
}
public class Auction {
private SortedSet<Bid> bids = new ConcurrentSkipListSet<Bid>();
public Bid getHighestBid() {
return bids.isEmpty() ? null : bids.first();
}
public void addBid(Bid bid) {
bids.add(bid);
}
}
Doing this has the following advantages:
Automatically provides a bidding history
Allows a simple way to save any other bid info you need
You could also consider this method:
/**
* #param bid
* #return true if the bid was successful
*/
public boolean makeBid(Bid bid) {
if (bids.isEmpty()) {
bids.add(bid);
return true;
}
if (bid.compareTo(bids.first()) <= 0) {
return false;
}
bids.add(bid);
return true;
}
Using an AtomicInteger is fine, provided you initialise it as Tomasz has suggested.
What you might like to think about, however, is whether all you will literally ever need to store is just the highest bid as an integer. Will you never need to store associated information, such as the bidding time, user ID of the bidder etc? Because if at a later stage you do, you'll have to start undoing your AtomicInteger code and replacing it.
I would be tempted from the outset to set things up to store arbitrary information associated with the bid. For example, you can define a "Bid" class with the relevant field(s). Then on each bid, use an AtomicReference to store an instance of "Bid" with the relevant information. To be thread-safe, make all the fields on your Bid class final.
You could also consider using an explicit Lock (e.g. see the ReentrantLock class) to control access to the highest bid. As Tomasz mentions, even with an AtomicInteger (or AtomicReference: the logic is essentially the same) you need to be a little careful about how you access it. The atomic classes are really designed for cases where they are very frequently accessed (as in thousands of times per second, not every few minutes as on a typical auction site). They won't really give you any performance benefit here, and an explicit Lock object might be more intuitive to program with.

Java: need advise about WeakHashMap

I guess I'm another person trying to make some kind of a cache with WeakHashMap. And I need some help with it.
I have bunch of TrackData objects that contain information about audio tracks. Then there are Track objects that keep reference to the TrackData inside. Several tracks can point to the same TrackData. Then I have TrackDataCache class that looks like this:
public class TrackDataCache {
private static TrackDataCache instance = new TrackDataCache();
public static TrackDataCache getInstance() {
return instance;
}
private WeakHashMap<TrackData, WeakReference<TrackData>> cache = new WeakHashMap<TrackData, WeakReference<TrackData>>();
public void cache(Track track) {
TrackData key = track.getTrackData();
WeakReference<TrackData> trackData = cache.get(key);
if (trackData == null) {
cache.put(key, new WeakReference<TrackData>(key));
} else {
track.setTrackData(trackData.get());
}
}
}
So when I load a track, I call TrackDataCache.cache() and if its track data was not loaded before, it is cached or replaced with cached copy otherwise (TrackData overrides equals() method to check for location and subsong index). I want to use weak references so that I don't need to care when I remove Tracks.
I wanted to ask if it is an ok practice to keep weak reference to the key in WeakHashMap, and if not, how should I approach this problem? I need weak references and constant time retrieving of cached values. I was thinking of copying WeakHashMap code and making getEntry() method public, which solves the problem but it's such a bad hack :(
PS. I understand that either apache or google collections may have something like this, but I really don't want to add 2Mb dependencies.
I'd recommend to replace WeakReferences with SoftReferences.
Any objects which is referenced only by a WeakReference is a target for every round of the garbage collector. It means that your cache can be cleared even it there's still enough free memory.
If you replace WeakReference with SoftReference then you state: Remove referenced object only when there's absolutely no free memory to allocate.
There's no ready-to-use SoftHashMap implementation in java. There is a good example in guava - MapMaker. It's worth to use this well-tested and verified on production environments code and not to provide your own definitely less quality implementation. It also has amazing mechanism of 'self-cleaning':
you can specify cache max size:
As the map size grows close to the
maximum, the map will evict entries
that are less likely to be used again.
For example, the map may evict an
entry because it hasn't been used
recently or very often.
you can specify expiration time for map entries with expireAfterWrite and expireAfterAccess methods.
I also find your cache design not very convenient. As I understand from your code snippet, from start your Tracks have strong references to their TrackData and you build your cache upon these circumstances. But from some moment you want to use your cache for retreiving data so you'll have to create new Tracks in some other way because from that moment you want to use cache but not strong references.
Different Tracks can have the same TrackData so we can't use Track as a key. So, I'd go with the next approach:
introduce intermediate ids level and make cache based on the Map<Integer, TrackData> with soft values and defined self-cleaning strategy (based on MapMaker);
change relation Track --> TrackData to Track --> Id (int). Cache Id --> TrackData.
TrackData can be shared by many instances of Track. We need to have a key system that doesn't require TrackData to obtain the same instance for several Track.
public class Track [
#Override
public int hashcode() {
... make hashcode that will be the same for
... tracks sharing the same track data.
}
#Override
public boolean equals() {
... ensure that if A.hashcode == B.hashcode then A.equals(B)
}
}
public class TrackDataManager {
private WeakHashMap<Track,TrackData> cache = new WeakHashMap<Track,TrackData>();
public TrackData getTrackData(Track track) {
// Track.hashcode()/equals() ensures two tracks that
// share track data will get the same object back
TrackData data = cache.get(track);
if (data == null) {
data = constructDataFromTrackFile(track);
cache.put(track, data);
}
return data;
}
private TrackData constructDataFromTrackFile(Track track) {
... read data from file and create that object.
}
}
If the construction of the TrackData object is always going to happen as part of reading the file, but the created instance is being thrown away in favour of the shared instance, I'd model that like this:
public class TrackData {
#Override
public int hashcode() {
... make hashcode that will be the same for same track data.
}
#Override
public boolean equals() {
... ensure that if A.hashcode == B.hashcode then A.equals(B)
}
}
public class TrackDataCache {
private WeakHashMap<Integer,TrackData> cache = new WeakHashMap<Integer,TrackData>();
public TrackData getTrackData(Track track) {
// cache contains shared TrackData instances, we may throw away
// the Track instance in favour of the shared one.
Integer key = track.getTrackData().hashcode();
TrackData data = cache.get(key);
if (data == null) {
cache.put(key, track.getTrackData());
data = track.getTrackData();
} else {
// ensure we're using the shared instance, not the local one.
// deliberate object reference comparison
if (data != track.getTrackData()) {
track.setTrackData(data);
}
}
return data;
}
}
Notice that the WeakHashMap will not do anything in any of the two solutions as long as there are Track objects alive keeping references to the TrackData. This could be fixed by making WeakReference inside Track - however that also means you can end up not having any TrackData, and need to read it back from file, in which case the first solution is better modelled than the second.

Categories