I am trying to refactor this code I have below that uses several nested if statements to check if two list contains the same items.
List<Car> CarList = CarService.findCarByConfigtype(pageName);
for (int i = 0; i < CarList.size(); i++) {
System.out.println(CarRestApiController.data().getModel());
if (CarList.get(i).getModel().equals(CarRestApiController.data().getModel())) {
dataFound.add(CarList.get(i).getModel());
if (CarList.get(i).getDerivative().equals(CarRestApiController.data().getDerivative())) {
dataFound.add(CarList.get(i).getDerivative());
if (CarList.get(i).getSvp().equals(CarRestApiController.data().getSvp())) {
dataFound.add(CarList.get(i).getSvp());
if (CarList.get(i).getEngine().equals(CarRestApiController.data().getEngine())) {
dataFound.add(CarList.get(i).getEngine());
if (CarList.get(i).getFueltype().equals(CarRestApiController.data().getFueltype())) {
dataFound.add(CarList.get(i).getFueltype());
if (CarList.get(i).getBodystyle().equals(CarRestApiController.data().getBodystyle())) {
dataFound.add(CarList.get(i).getBodystyle());
if (CarList.get(i).getTransmission().equals(CarRestApiController.data().getTransmission())) {
dataFound.add(CarList.get(i).getTransmission());
if (CarList.get(i).getSalescategory().equals(CarRestApiController.data().getSalescategory())) {
dataFound.add(CarList.get(i).getSalescategory());
}
}
}
}
}
}
}
}
}
A solution could be to use Strategy design pattern. Have a strategy for each if statement, iterate over the list of strategies and process each car in the list
public interface CarFeatureStrategy {
boolean canProcess(Car carToProcess, Car carToMatch);
Object process(Car carToProcess);
}
The canHandle method should encapsulate the if statements which needs to be true to allow the processing and the process method should return the value of corresponding property of the car (for the example in the description there should be 8 strategies)
public class ModelStrategy implements CarFeatureStrategy {
#Override
public boolean canProcess(Car carToProcess, Car carToMatch) {
return carToProcess.getModel().equals(carToMatch.getModel));
}
#Override
public Object process(Car carToProcess) {
return carToProcess.getModel();
}
}
public class DerivativeStrategy implements CarFeatureStrategy {
#Override
public boolean canProcess(Car carToProcess, Car carToMatch) {
return carToProcess.getModel().equals(carToMatch.getModel())
&& carToProcess.getDerivative().equals(carToMatch.getDerivative());
}
#Override
public Object process(Car carToProcess) {
return carToProcess.getDerivative();
}
}
public class SvpStrategy implements CarFeatureStrategy {
#Override
public boolean canProcess(Car carToProcess, Car carToMatch) {
return carToProcess.getModel().equals(carToMatch.getModel())
&& carToProcess.getDerivative().equals(carToMatch.getDerivative())
&& carToProcess.getSvp().equals(carToMatch.getSvp());
}
#Override
public Object process(Car carToProcess) {
return carToProcess.getSvp();
}
}
// .... and so on for each condition which needs to be met
// EngineStrategy, FueltypeStrategy, BodystyleStrategy,
// TransmissionStrategy, SalescategoryStrategy
The CarProcessor retrieves the cars corresponding to the given pageName, retrieves the data from the CarRestApiController and uses the list of strategies to process the cars
public class CarProcessor {
private CarService carService;
private CarRestApiController restController;
private List<CarFeatureStrategy> carFeatureStrategies;
public void processCars(Object pageName) {
// for example purpose the list of strategies is initialized here,
// but it should be initialized somwhere where the initialization is done
// only once rather than each time the processCars method is called
carFeatureStrategies = new ArrayList<>();
carFeatureStrategies.add(new ModelStrategy());
carFeatureStrategies.add(new DerivativeStrategy());
carFeatureStrategies.add(new SvpStrategy());
// ....
// add to the strategies list an instance of each strategy to process
// the car
Car carToMatch = restController.data();
List<Car> cars = carService.findCarByConfigtype(pageName);
List<Object> dataFound = new ArrayList<>();
for (Car carToProcess : cars) {
for (CarFeatureStrategy carFeatureStrategy : carFeatureStrategies) {
if (carFeatureStrategy.canProcess(carToProcess, carToMatch)) {
dataFound.add(carFeatureStrategy.process(carToProcess));
}
}
}
}
}
The example could be optimised implementing the Chain of responsibility design pattern. With a chain of responsibility the if statements in the canHandle methods will simplify to only one boolean condition per strategy.
For chain of responsibility the strategies have to be enhanced with a method to return the next strategy in the chain
public interface CarFeatureStrategy {
boolean canProcess(Car carToProcess, Car carToMatch);
Object process(Car carToProcess);
CarFeatureStrategy next();
}
the strategy implementations have to be enhanced with a reference to the next strategy in the chain
public class ModelStrategy implements CarFeatureStrategy {
private CarFeatureStrategy nextStrategy;
public ModelStrategy(CarFeatureStrategy nextStrategy) {
this.nextStrategy = nextStrategy;
}
#Override
public boolean canProcess(Car carToProcess, Car carToMatch) {
// check only the model
return carToProcess.getModel().equals(carToMatch.getModel));
}
#Override
public Object process(Car carToProcess) {
return carToProcess.getModel();
}
#Override
public CarFeatureStrategy next() {
return this.nextStrategy;
}
}
public class DerivativeStrategy implements CarFeatureStrategy {
private CarFeatureStrategy nextStrategy;
public DerivativeStrategy(CarFeatureStrategy nextStrategy) {
this.nextStrategy = nextStrategy;
}
#Override
public boolean canProcess(Car carToProcess, Car carToMatch) {
// check only the derivative property
return carToProcess.getDerivative().equals(carToMatch.getDerivative());
}
#Override
public Object process(Car carToProcess) {
return carToProcess.getDerivative();
}
#Override
public CarFeatureStrategy next() {
return this.nextStrategy;
}
}
// ... and so on for all the strategies
The CarProcessor should build a chain of strategies and process each car until the chain is finished (the next method of current strategy returns null) or the current strategy cannot process the current car (canHandle method of current strategy returns false)
public class CarProcessor {
private CarService carService;
private CarRestApiController restController;
public void processCars(Object pageName) {
// for example purpose the chain of responsibilities is initialized here,
// but it should be initialized somwhere where the initialization is done
// only once rather than each time the processCars method is called
// initialise the chain of responsibilities in revers order
CarFeatureStrategy salesCategoryStrategy = new SalescategoryStrategy(null);
CarFeatureStrategy transmissionStrategy = new TransmissionStrategy(salesCategoryStrategy);
CarFeatureStrategy bodystyleStrategy = new BodystyleStrategy(transmissionStrategy);
CarFeatureStrategy fueltypeStrategy = new FueltypeStrategy(bodystyleStrategy);
CarFeatureStrategy engineStrategy = new EngineStrategy(fueltypeStrategy);
// .... and so on until the first strategy in the chain
CarFeatureStrategy modelStrategy = new ModelStrategy(...);
Car carToMatch = restController.data();
List<Car> cars = carService.findCarByConfigtype(pageName);
List<Object> dataFound = new ArrayList<>();
for (Car carToProcess : cars) {
CarFeatureStrategy currentStrategy = modelStrategy;
do {
if ( !currentStrategy.canProcess(carToProcess, carToMatch)) {
// if current strategy cannot process the current car
// stop the chain
break;
}
dataFound.add(currentStrategy.process(carToProcess));
// move to the next strategy in the chain
currentStrategy = currentStrategy.next();
} while (currentStrategy != null)
}
}
}
I would first save the result of CarList.get(i) and CarRestApiController.data() to variables as #T.J.Crowder suggested. Then I would flip the if-checks and use continue to get rid of the nesting. Like this:
List<Car> carList = CarService.findCarByConfigtype(pageName);
for (int i = 0; i < carList.size(); i++) {
Car apiData = CarRestApiController.data();
Car carListData = carList.get(i);
System.out.println(CarRestApiController.data().getModel());
if (!carListData.getModel().equals(apiData.getModel())) {
continue;
}
dataFound.add(carListData.getModel());
if (!carListData.getDerivative().equals(apiData.getDerivative())) {
continue;
}
dataFound.add(carListData.getDerivative());
if (!carListData.getSvp().equals(apiData.getSvp())) {
continue;
}
dataFound.add(carListData.getSvp());
// ... and so on.
}
Related
In my scenario, we offer multiple plans to customers. (planA, planB, planC etc.) planA is lower than planB and planB is lower than planC. A customer can move from lower plan to higher plan but not vice-versa. If a customer is on planA and wants to 'activate' planB, then planA must be cancelled. Essentially, a plan can be 'activated' and 'deactivated'. I had 2 designs in mind.
interface Plan {
activate();
deactivate();
}
This interface will be inherited by each plans' (planA, planB, planC, etc). The activate method would be inherited and look something like this:
activate() {
Plan planToCancel = getLowerGradePlanToCancel()
planToCancel.cancel();
// perform business logic to activate plan.
}
Option B is something similar to strategy pattern: I have 2 interfaces:
interface Activate {
activate();
}
interface Deactivate {
deactivate()
}
Each of the plans will inherit those interfaces. Then my business logic would look something like this:
activatePlan(planName, planToDeactivate) {
Activate activate = Factory.getActivateInstanceForPlan(planName);
DeActivate dectivate = Factory.getActivateInstanceForPlan(planToDeactivate);
deactivate.deactivate();
activate.activate();
}
Of the two designs which one is more appropriate (Object Oriented) and why ? The only thing in code that is likely to change is more plans will be added in future.
You have 3 plans. Plan C can't go higher and similarly plan A can't go lower. Plan B can do both operations. Use one interface and put activate and deactivate methods there. You already mentioned that on option A. Use template pattern there to give an opportunity to change their behaviours for your plans. This will be appropriate if you will add another plans later on. This will help you a lot when you add another plans.
If you will have only three plans, then second option is more appropriate. Since you have only 3 plans and only one of them using activate and deactivate together, then you don't need to implement both of the methods, interfaces. This will decrease the dependencies of your code.
Pick the best choice for your case.
I have a different approach in mind where you have a class that manages all the plans, while plan interface is encapsulated and only reveals the necessary of its API.
I think this approach will have minimal code modification for each added Plan, moreover, it can prevent user from making mistakes (e.g. downgrading a plan).
The essential interfaces:
interface Plan {
public Plan next();
public boolean isActivated();
// for debug purposes
public String planDescription();
}
interface PlansManager {
public Plan nextPlan(Plan current);
}
The basic idea is to have some SemiConcretePlan class which implements the static (mutual) behaviour in all plans, the public API is next & isActivated while activate and cancel methods private (you don't want the user to cancel a plan without switching to the next or to activated a cancelled one be keeping a previous Plan pointer on it) and only the PlansManager or the Plan itself will handle the activation and cancellation, PlansManager activates the first plan and returns it and next method uses PlansManager to get the next and only the SemiConcertePlan activate the current and cancels the previous Plan.
Here the SemiConcertePlan:
abstract class SemiConcretePlan implements Plan {
private PlansManager m_plansManager;
private boolean m_isActivated;
private int m_id;
private static int s_idGenerator = 0, s_firstActivatedId = 1;
public SemiConcretePlan(PlansManager plansManager){
m_plansManager = plansManager;
m_id = generateId();
m_isActivated = (m_id == s_firstActivatedId);
}
private int generateId() {
return ++s_idGenerator;
}
private void activatePlan() {
this.m_isActivated = true;
}
private void cancelPlan() {
this.m_isActivated = false;
}
public boolean isActivated() {
return this.m_isActivated;
}
public Plan next() {
this.cancelPlan();
SemiConcretePlan nextPlan = (SemiConcretePlan) m_plansManager.nextPlan(this);
nextPlan.activatePlan();
return nextPlan;
}
public boolean equals(Object other) {
if (this == other)
return true;
if (other == null || !(other instanceof SemiConcretePlan) || this.hashCode() != other.hashCode())
return false;
SemiConcretePlan otherPlan = ((SemiConcretePlan) other);
if (m_id != ((SemiConcretePlan) otherPlan).m_id)
return false;
return true;
}
public abstract int hashCode();
public abstract String planDescription();
}
planDescription method is an example of dynamic method, hashCode is needed for class PlansManager to hash plans in map which map current plan to higher (next) plan.
Here is the AscedingPlansManager class:
class AscedingPlansManager implements PlansManager{
private List<Plan> m_plansList;
private Map<Plan, Plan> m_planToHigherPlanMapping;
public AscedingPlansManager() {
m_plansList = new LinkedList();
m_planToHigherPlanMapping = new HashMap();
Plan[] plans = {
new PlanA(this),
new PlanB(this),
new PlanC(this),
new PlanD(this)
};
for(int i = 0; i < plans.length - 1; ++i) {
m_plansList.add(plans[i]);
m_planToHigherPlanMapping.put(plans[i], plans[i+1]);
}
m_plansList.add(plans[plans.length - 1]);
m_planToHigherPlanMapping.put(plans[plans.length - 1], plans[plans.length - 1]);
}
public Plan nextPlan(Plan current) {
return m_planToHigherPlanMapping.getOrDefault(current, null);
}
private void activatePlan(Plan plan) {
try {
Method privateActivateMethod = SemiConcretePlan.class.getDeclaredMethod("activatePlan");
privateActivateMethod.setAccessible(true);
privateActivateMethod.invoke(plan);
} catch(Exception e) {
e.printStackTrace();
}
}
public void cancelAll() {
for(Plan plan: m_plansList)
try {
Method privateActivateMethod = SemiConcretePlan.class.getDeclaredMethod("cancelPlan");
privateActivateMethod.setAccessible(true);
privateActivateMethod.invoke(plan);
} catch(Exception e) {
e.printStackTrace();
}
}
public Plan firstPlan() {
Plan first = m_plansList.get(0);
this.activatePlan(first);
return first;
}
public boolean[] plansToActivationState() {
boolean[] ret = new boolean[m_plansList.size()];
int index = 0;
for(Plan plan: m_plansList)
ret[index++] = plan.isActivated();
return ret;
}
}
I know that this is huge code, but I think it will make add plans easy, you will only need to change the hashCode method, the sequence of the plans can be changed in the constructor of AscedingPlansManager or creating a different manger class from scratch.
Here is the full code, you can see how little changes I needed to do for class PlanD:
import java.util.;
import java.lang.reflect.;
interface Plan {
public Plan next();
public boolean isActivated();
// for debug purposes
public String planDescription();
}
interface PlansManager {
public Plan nextPlan(Plan current);
}
abstract class SemiConcretePlan implements Plan {
private PlansManager m_plansManager;
private boolean m_isActivated;
private int m_id;
private static int s_idGenerator = 0, s_firstActivatedId = 1;
public SemiConcretePlan(PlansManager plansManager){
m_plansManager = plansManager;
m_id = generateId();
m_isActivated = (m_id == s_firstActivatedId);
}
private int generateId() {
return ++s_idGenerator;
}
private void activatePlan() {
this.m_isActivated = true;
}
private void cancelPlan() {
this.m_isActivated = false;
}
public boolean isActivated() {
return this.m_isActivated;
}
public Plan next() {
this.cancelPlan();
SemiConcretePlan nextPlan = (SemiConcretePlan) m_plansManager.nextPlan(this);
nextPlan.activatePlan();
return nextPlan;
}
public boolean equals(Object other) {
if (this == other)
return true;
if (other == null || !(other instanceof SemiConcretePlan) || this.hashCode() != other.hashCode())
return false;
SemiConcretePlan otherPlan = ((SemiConcretePlan) other);
if (m_id != ((SemiConcretePlan) otherPlan).m_id)
return false;
return true;
}
public abstract int hashCode();
public abstract String planDescription();
}
class AscedingPlansManager implements PlansManager{
private List<Plan> m_plansList;
private Map<Plan, Plan> m_planToHigherPlanMapping;
public AscedingPlansManager() {
m_plansList = new LinkedList();
m_planToHigherPlanMapping = new HashMap();
Plan[] plans = {
new PlanA(this),
new PlanB(this),
new PlanC(this),
new PlanD(this)
};
for(int i = 0; i < plans.length - 1; ++i) {
m_plansList.add(plans[i]);
m_planToHigherPlanMapping.put(plans[i], plans[i+1]);
}
m_plansList.add(plans[plans.length - 1]);
m_planToHigherPlanMapping.put(plans[plans.length - 1], plans[plans.length - 1]);
}
public Plan nextPlan(Plan current) {
return m_planToHigherPlanMapping.getOrDefault(current, null);
}
private void activatePlan(Plan plan) {
try {
Method privateActivateMethod = SemiConcretePlan.class.getDeclaredMethod("activatePlan");
privateActivateMethod.setAccessible(true);
privateActivateMethod.invoke(plan);
} catch(Exception e) {
e.printStackTrace();
}
}
public void cancelAll() {
for(Plan plan: m_plansList)
try {
Method privateActivateMethod = SemiConcretePlan.class.getDeclaredMethod("cancelPlan");
privateActivateMethod.setAccessible(true);
privateActivateMethod.invoke(plan);
} catch(Exception e) {
e.printStackTrace();
}
}
public Plan firstPlan() {
Plan first = m_plansList.get(0);
this.activatePlan(first);
return first;
}
public boolean[] plansToActivationState() {
boolean[] ret = new boolean[m_plansList.size()];
int index = 0;
for(Plan plan: m_plansList)
ret[index++] = plan.isActivated();
return ret;
}
}
class PlanA extends SemiConcretePlan {
public PlanA(PlansManager plansManager) {
super(plansManager);
}
public int hashCode() {
return 1;
}
public String planDescription() {
return "This is PlanA";
}
}
class PlanB extends SemiConcretePlan {
public PlanB(PlansManager plansManager) {
super(plansManager);
}
public int hashCode() {
return 2;
}
public String planDescription() {
return "This is PlanB";
}
}
class PlanC extends SemiConcretePlan {
public PlanC(PlansManager plansManager) {
super(plansManager);
}
public int hashCode() {
return 3;
}
public String planDescription() {
return "This is PlanC";
}
}
class PlanD extends SemiConcretePlan {
public PlanD(PlansManager plansManager) {
super(plansManager);
}
public int hashCode() {
return 4;
}
public String planDescription() {
return "This is PlanD";
}
}
public class Main{
public static void main(String []args){
AscedingPlansManager ascedingPlansManager = new AscedingPlansManager();
Plan currentPlan = ascedingPlansManager.firstPlan();
int i = 0, maxIterations = 5;
while((++i) <= maxIterations) {
System.out.println(currentPlan.planDescription());
System.out.println(Arrays.toString(ascedingPlansManager.plansToActivationState()));
currentPlan = currentPlan.next();
}
ascedingPlansManager.cancelAll();
System.out.println("After canceling all plans");
System.out.println(Arrays.toString(ascedingPlansManager.plansToActivationState()));
}
}
I still not sure of my implementation, I usually access private method in c++ with friend modifier, if you want to discuss anything feel free to do so.
I have implemented the Composite Design Pattern and then expanded the Composite class to also implement Iterable, however the iterator() method (which returns an iterator object) is also part of the abstract Component class and is then implemented by the Composite class (but not the Leaf class).
I want to implement a depth first and breadth first search for a tree-like structure. See summarized code below:
public abstract class Component {
public void add() {
}
public void remove() {
}
public ArrayList<Component> getItems() {
}
public ItemIterator iterator() {
}
public class Composite extends Component implements Iterable<Component> {
ArrayList<Component> items = new ArrayList<Component>();
String name;
public ItemIterator iterator() {
return new ItemIterator(this);
}
public Composite(String name) {
this.name = name;
}
public getName() {
// returns name
}
public ArrayList<Component> getItems() {
return this.items;
}
public class ItemIterator implements Iterator<Component> {
ArrayList<Component> breadthFirstSearch = new ArrayList<Component>();
Component currentItem;
public ItemIterator(Component firstItem) {
currentItem = firstItem;
breadthFirstSearch.add(currentItem);
}
public boolean hasNext() {
if (breadthFirstSearch.isEmpty()) {
return false;
}
return true;
}
public Component next() {
// This method pops the root item the first time, creates its children,
// places at end of ArrayList,
// then returns the root. Second time the same operations are performed
// on the following item in the breadth first traversal of the tree.
if (hasNext()) {
Component nextItem = breadthFirstSearch.get(0);
if (nextItem instanceof Composite) {
for (Component item : currentItem.getItems()) {
breadthFirstSearch.add(item);
}
}
breadthFirstSearch.remove(0);
if (hasNext()) {
currentItem = breadthFirstSearch.get(0);
}
return nextItem;
}
return null;
}
public class Demo {
public static void main(String[] args) {
Component bag = new Composite("bag");
Component plasticBag = new Composite("plastic bag");
Component makeupBag = new Composite("makeup bag");
Component phone = new Composite("phone");
Component lipstick = new Composite("lipstick");
Component mascara = new Composite("mascara");
bag.add(plasticBag); bag.add(makeupBag);
plasticbag.add(phone); makeupBag.add(lipstick); makeupBag.add(mascara);
ItemIterator itr = bag.iterator();
while (itr.hasNext()) {
System.out.println(itr.next().getName());
}
}
}
The code above compiles and runs fine, it works. However, I am not certain of whether it is programmatically acceptable. The structure of it seems to fundamentally go against other Iterator implementations that I have seen (implementations that I discovered after finishing the above solution), but I can't quite grasp/explain what is so wrong about it. The other way of implementing Iterable (in a different context) was of the form:
public abstract class Component {
public void add() {
}
public void remove() {
}
public ArrayList<Component> getItems() {
}
}
Note the lack of an iterator() method in the abstract class above.
public class Composite extends Component implements Iterable<Component> {
ArrayList<Component> items = new ArrayList<Component>();
String name;
public Iterator<Component> iterator() {
return new Iterator() {
public boolean hasNext() {
// Code
}
public Iterator<Component> next() {
// Code
};
}
public Composite(String name) {
this.name = name;
}
public getName() {
// returns name
}
public ArrayList<Component> getItems() {
return this.items;
}
}
Which way of structuring the solution is better, and is my way of doing it outright wrong/bad practice and if so, why? I am new to Java, so I apologize if this turns out to be a bad question.
I think you described the visitor pattern:
interface Visitable {
void accept(Visitor v);
}
class Visitor {
void visit(Component c){
c.doFooBar();// implement your logic here
}
}
class Component implements Visitable {
private List<Component> children;
void accept(Visitor v){
v.visit(this);
children.forEach(child -> child.accept(v)); // sumbit the visitor/iterator down the composite tree
}
}
public static void main(String[] args){
Component composite = Factory.createComposite();
composite.accept(new Visitor());
}
Instead of having iterator build up a list of pending items to iterate, it should just store a list of pending iterators to traverse.
Here is a Minimal, Reproducible Example:
public final class Node {
private final String name;
private List<Node> children = new ArrayList<>();
public Node(String name) {
this.name = name;
}
public Node(String name, Node... children) {
this.name = name;
this.children.addAll(Arrays.asList(children));
}
public String getName() {
return this.name;
}
public List<Node> getChildren() {
return this.children;
}
public Iterable<Node> breadthFirstSearch() {
return () -> new NodeIterator(this, true);
}
public Iterable<Node> depthFirstSearch() {
return () -> new NodeIterator(this, false);
}
#Override
public String toString() {
return "Node[" + this.name + "]";
}
}
public final class NodeIterator implements Iterator<Node> {
private final Deque<Iterator<Node>> iterators = new ArrayDeque<>();
private final boolean breadthFirst;
public NodeIterator(Node node, boolean breadthFirst) {
this.iterators.add(Collections.singleton(node).iterator());
this.breadthFirst = breadthFirst;
}
#Override
public boolean hasNext() {
return ! this.iterators.isEmpty();
}
#Override
public Node next() {
Iterator<Node> iterator = this.iterators.removeFirst();
Node node = iterator.next();
if (iterator.hasNext())
this.iterators.addFirst(iterator);
if (! node.getChildren().isEmpty()) {
if (this.breadthFirst)
this.iterators.addLast(node.getChildren().iterator());
else
this.iterators.addFirst(node.getChildren().iterator());
}
return node;
}
}
Test
Node root = new Node("root",
new Node("1",
new Node("1.1",
new Node("1.1.1"),
new Node("1.1.2")),
new Node("1.2",
new Node("1.2.1"),
new Node("1.2.2"))
),
new Node("2",
new Node("2.1",
new Node("2.1.1"),
new Node("2.1.2")),
new Node("2.2",
new Node("2.2.1"),
new Node("2.2.2"))));
for (Node node : root.breadthFirstSearch())
System.out.println(node);
System.out.println();
for (Node node : root.depthFirstSearch())
System.out.println(node);
Output
Node[root]
Node[1]
Node[2]
Node[1.1]
Node[1.2]
Node[2.1]
Node[2.2]
Node[1.1.1]
Node[1.1.2]
Node[1.2.1]
Node[1.2.2]
Node[2.1.1]
Node[2.1.2]
Node[2.2.1]
Node[2.2.2]
Node[root]
Node[1]
Node[1.1]
Node[1.1.1]
Node[1.1.2]
Node[1.2]
Node[1.2.1]
Node[1.2.2]
Node[2]
Node[2.1]
Node[2.1.1]
Node[2.1.2]
Node[2.2]
Node[2.2.1]
Node[2.2.2]
I often have to deal with DTOs that contains other DTOs and I'd like to scan one object's attributes (and their own attributes, recursively) and retrieve every accessible object of class Bingo in the whole hierarchy.
For example, when I have the following :
public static class Bingo {
// the one I want to get
}
public static class Foo {
private Bar bar;
private Bingo bingo;
private List<Bingo> bingos;
// getters & setters
}
public static class Bar {
private Bingo bingo;
// getters & setters
}
I'd like to get all instances of Bingo found in attributes of my Foo object, including the ones in the Bar object and the List.
Is there a library conveniently doing that ?
A more complete test case (using a bit of JUnit) :
public static class Bingo {
private final int id;
public Bingo(int in_id) {
id = in_id;
}
#Override
public String toString() {
return "Bingo#"+String.valueOf(id);
}
}
public static class BingoWrapper {
private Bingo bingo;
public Bingo getBingo() {
return bingo;
}
public void setBingo(Bingo in_bingo) {
bingo = in_bingo;
}
}
public static class BingoFactory {
private final List<Bingo> ALL_BINGOS = new ArrayList<>();
private int sequence = 0;
public Bingo createBingo(){
Bingo l_bingo = new Bingo(sequence++);
ALL_BINGOS.add(l_bingo);
return l_bingo;
}
public BingoWrapper createBingoWrapper(){
BingoWrapper l_bar = new BingoWrapper();
l_bar.setBingo(createBingo());
return l_bar;
}
public List<Bingo> getAllBingos(){
return ALL_BINGOS.stream().collect(Collectors.toList());
}
}
public static class Foo {
private Bingo bingo;
private BingoWrapper wrapper;
private Bingo[] array;
private Collection<Object> collection;
private Map<Object,Object> map;
public Bingo getBingo() {
return bingo;
}
public void setBingo(Bingo in_bingo) {
bingo = in_bingo;
}
public BingoWrapper getWrapper() {
return wrapper;
}
public void setWrapper(BingoWrapper in_bar) {
wrapper = in_bar;
}
public Bingo[] getArray() {
return array;
}
public void setArray(Bingo[] in_array) {
array = in_array;
}
public Collection<Object> getCollection() {
return collection;
}
public void setCollection(Collection<Object> in_collection) {
collection = in_collection;
}
public Map<Object, Object> getMap() {
return map;
}
public void setMap(Map<Object, Object> in_map) {
map = in_map;
}
}
#Test
public void test(){
BingoFactory l_bingoFactory = new BingoFactory();
Foo l_foo = new Foo();
l_foo.setBingo(l_bingoFactory.createBingo()); // one in a field
l_foo.setWrapper(l_bingoFactory.createBingoWrapper()); // one in a field of a field
l_foo.setArray(new Bingo[]{l_bingoFactory.createBingo()}); // one in an array in a field
l_foo.setCollection(Arrays.asList(
l_bingoFactory.createBingo(), // one in Collection in a field
l_bingoFactory.createBingoWrapper())); // one in a field of an item in a Collection in a field
Map<Object,Object> l_map = new HashMap<>();
l_foo.setMap(l_map);
l_map.put("key", l_bingoFactory.createBingo()); // one as a key in a Map in a field
l_map.put(l_bingoFactory.createBingo(), "value"); // one as a value in a Map in a field
l_map.put("keyAgain", l_bingoFactory.createBingoWrapper()); // one wrapped in a value in a Map in a Field
l_map.put(l_bingoFactory.createBingoWrapper(), "valueAgain"); // one wrapped in a key in a Map in a field
List<Bingo> l_found = BeanUtils.scanObjectForType(l_foo, Bingo.class); // Magic happens here
System.out.println(l_found); // for debug
Assert.assertTrue(l_found.containsAll(l_bingoFactory.getAllBingos())); // I want them ALL
}
A solution with Spring's BeanUtils : (I've added a boolean to decide whereas objects of input class needed to be scanned or not. (i.e. do you expect your Bingo objects to contain other objects of type Bingo ?))
public static <T> List<T> scanObjectForType(Object in_object, Class<T> in_type, boolean in_scanSameType){
return scanObjectForType(in_object, in_type, in_scanSameType, new HashSet<>());
}
private static <T> List<T> scanObjectForType(Object in_object, Class<T> in_type, boolean in_scanSameType, Set<Object> in_alreadyScanned){
if(in_type == null){
throw new IllegalArgumentException("in_type should not be null");
}
if(in_object instanceof Class){
throw new IllegalArgumentException("in_type should not be a Class");
}
if(in_object == null || in_alreadyScanned.contains(in_object)){
return Collections.emptyList();
}
in_alreadyScanned.add(in_object); // to prevent infinite loop when inner object references outer object
if(in_type.isInstance(in_object)){
return Collections.singletonList((T) in_object);
}
List<T> l_result = new ArrayList<>();
if(in_type.isInstance(in_object)){
l_result.add((T) in_object);
if(!in_scanSameType){
return l_result;
}
}
if(in_object instanceof Object[]){
for(Object l_item : (Object[]) in_object){
l_result.addAll(scanObjectForType(l_item, in_type, in_scanSameType, in_alreadyScanned));
}
} else if(in_object instanceof Collection){
for(Object l_item : (Collection<Object>) in_object){
l_result.addAll(scanObjectForType(l_item, in_type, in_scanSameType, in_alreadyScanned));
}
} else if(in_object instanceof Map){
Map<Object,Object> l_map = (Map<Object,Object>) in_object;
for(Map.Entry<Object, Object> l_entry : l_map.entrySet()){
l_result.addAll(scanObjectForType(l_entry.getKey(), in_type, in_scanSameType, in_alreadyScanned));
l_result.addAll(scanObjectForType(l_entry.getValue(), in_type, in_scanSameType, in_alreadyScanned));
}
} else {
PropertyDescriptor[] l_descriptors = org.springframework.beans.BeanUtils.getPropertyDescriptors(in_object.getClass());
for(PropertyDescriptor l_descriptor : l_descriptors){
Method l_readMethod = l_descriptor.getReadMethod();
if(l_readMethod != null){
try {
Object l_readObject = l_readMethod.invoke(in_object);
if(l_readObject != null
&& !l_readObject.equals(in_object) // prevents infinite loops
&& !(l_readObject instanceof Class)){ // prevents weird loops when accessing properties of classes
l_result.addAll(scanObjectForType(l_readObject,in_type, in_scanSameType, in_alreadyScanned));
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// too bad but continue
LOGGER.warn("Got an error trying to access field : ", e);
continue;
}
}
}
}
return l_result;
}
Its limitations :
Only scan properties with public accessors
Does not scan Class types (to prevent scanning of the whole ClassLoader's classes, and because the use-case is DTO-oriented).
Relies on recursivity. I guess it might be prettier to implement a BeanVisitor object that operates on a loop over a Set of nested beans.
Will scan Objects returned by getter methods that may not be properties.
It's not tested with inheritence.
I'm writing a game-like program and it has a class that has to act as an item. Normally I would just keep it as an item but every one has to wait 10 game-days in order to start doing what it's meant to. Is there a way to update the days in all the classes at once? I've tried to use a static method but you can't use instance variables in those so it didn't work. I've thought about possibly making a code that expands every time a new instance is made but I can't find anything i can understand about it. Is there any way to add to a method, make this an item, anything? This is what I have at the moment:
public class Tree
{
private boolean fullGrown;
private int day;
private int APDay; //apples per day
private static int totalApples;
public Tree()
{
fullGrown = false;
day = 0;
APDay = (int) (Math.random()*2) + 4;
}
public void updateDay()
{
day = day + 1;
if (day == 10) fullGrown = true;
if (fullGrown == true) totalApples = totalApples + APDay;
}
public void skipGrowth()
{
fullGrown = true;
}
}
Although this works, you have to update the day for every instance separately. I need a way to update all the instances at the same time. This will also be used by a code and not the actual interface, just in case that's helpful.
Observer and factory pattern looks like a good candidate here.
I hope the code below explain rest to you
public interface DayEventListener {
public void onDay10(DayEvent DayEvent);
}
public class DayEvent {
//... pojo
}
public class AwesomeGame {
private List<DayEventListener> dayEventListenerList = new ArrayList<>();
public void addDayListener(DayEventListener del) {
dayEventListener.add(del);
}
public void fireDay10Event(DayEvent de) {
for(DayEventListener del : dayEventListenerList) {
del.onDay10(de);
}
}
public class Item implements DayEventListener {
//All constructors should be private
public static Item buildItem() {
Item Item = new Item();
awesomeGame.addDayEventListener(Item);
return item;
}
}
so I did some research and while I was working on another project I discovered that i could use a vector to keep track of all my instances, so here is that class:
import java.util.Vector;
public class catcher
{
private static Vector allInstances = new Vector();
private int catchLeft;
private String name;
public catcher(String name)
{
catchLeft = Integer.parseInt(name.substring(name.indexOf("#") + 1, name.length()));
catchLeft--;
this.name = name;
if (catchLeft != 0) allInstances.add(this);
}
public static synchronized Vector getAllInstances()
{
return (Vector) (allInstances.clone());
}
public boolean check(String name, boolean change)
{
boolean foo;
if (this.name.equals(name))
{
if (change == true) catchLeft--;
foo = true;
}
else foo = false;
if (catchLeft <= 0) this.finalize();
return foo;
}
public void finalize()
{
allInstances.removeElement(this);
}
public static void clear()
{
allInstances.clear();
}
}
now that I have a record of all the instances, I used this method in another class to assess all the instances:
import java.util.Iterator;
import java.util.Vector;
public class recipe
{
private boolean checkForList(String name, boolean add)
{
Iterator list = catcher.getAllInstances().iterator();
boolean running = true;
boolean booleanReturn = true;
while (running == true)
{
if (list.hasNext())
{
catcher Foo = (catcher) (list.next());
if (Foo.check(name, false) == true)
{
Foo.check(name, true);
running = false;
booleanReturn = true;
}
}
else
{
if (add == true) new catcher(name);
running = false;
booleanReturn = false;
}
}
return booleanReturn;
}
}
I'm sure that this can be modified to update the classes instead of just accessing them.
#Entity
#NamedQueries({
#NamedQuery(
name = "FolderNode.findByName",
query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode = :parentNode"),
#NamedQuery(
name = "FolderNode.findRootNodeByName",
query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode is null")
})
public class FolderNode extends InstructorTreeNode {
public FolderNode() {
super();
}
public FolderNode(String name) {
this();
setName(name);
}
public FolderNode(int sortOrder, String name) {
this(name);
this.sortOrder = sortOrder;
}
public FolderNode(int sortOrder, String name, EmployeeState status) {
this(sortOrder, name);
this.status = status;
}
public static FolderNode addWaitingListNode(String name) {
EntityManager em = getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListNode = getWaitingListFolder();
FolderNode folderNode = new FolderNode(0, name);
waitingListNode.addChild(folderNode);
em.merge(waitingListNode);
em.getTransaction().commit();
em.close();
return folderNode;
}
public static void addWaitingListStudent(String waitingList, Student s) {
EntityManager em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListsNode = getWaitingListFolder();
FolderNode waitingListNode = getDao().findFolderNodeByName(waitingListsNode, waitingList);
waitingListNode.addChild(new EmployeeLeaf(s.getInmate()));
em.merge(waitingListNode);
em.getTransaction().commit();
em.close();
}
public static FolderNode getAMClassFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "AM Class");
}
public static FolderNode getAttendanceFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Employee Attendance");
}
public static FolderNode getFormerParaprosFolder() {
return getDao().findFolderNodeByName(getParaprosFolder(), "Former");
}
public static FolderNode getFormerStudentsFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "Former");
}
public static FolderNode getPMClassFolder() {
return getDao().findFolderNodeByName(getStudentsFolder(), "PM Class");
}
public static FolderNode getParaprosFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Parapros");
}
public static FolderNode getPendingStudentsFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Pending Students");
}
public static FolderNode getRootFolder() {
return getDao().findFolderNodeByName(null, EducationPreferences.getInstructor().getInstructorName());
}
public static FolderNode getStudentsFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Students");
}
public static FolderNode getWaitingListFolder(String name) {
FolderNode waitingListsNode = getWaitingListFolder();
return getDao().findFolderNodeByName(waitingListsNode, name);
}
public static FolderNode getWaitingListFolder() {
return getDao().findFolderNodeByName(getRootFolder(), "Waiting List");
}
public static void setClassFolder(Student aStudent, EntityManager entityManager) {
EntityManager em = entityManager;
if (entityManager == null) {
em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
}
EmployeeLeaf leaf = EmployeeLeaf.findActiveStudentLeaf(aStudent);
FolderNode node = aStudent.getShift() == Shift.AM ? getAMClassFolder() : getPMClassFolder();
leaf.setParentNode(node);
em.merge(leaf);
GlobalEntityMethods.updateHistory(leaf);
if (entityManager == null) {
em.getTransaction().commit();
em.close();
}
}
public static void transferWaitingListStudent(String currentFolder, String toFolder, Student student) {
EntityManager em = FolderNode.getDao().getEntityManager();
em.getTransaction().begin();
FolderNode waitingListsNode = getWaitingListFolder();
FolderNode currentWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, currentFolder);
EmployeeLeaf employeeLeaf = EmployeeLeaf.getDao().findWaitingListLeafByInmate(student.getInmate());
currentWaitingListNode.removeChild(employeeLeaf);
FolderNode toWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, toFolder);
toWaitingListNode.addChild(employeeLeaf);
em.merge(currentWaitingListNode);
em.merge(toWaitingListNode);
em.getTransaction().commit();
em.close();
}
public void addChild(InstructorTreeNode node) {
childNodes.add(node);
node.setParentNode(this);
}
public List<InstructorTreeNode> getChildNodes() {
Collections.sort(childNodes);
return childNodes;
}
#Override
public Set<Inmate> getInmates() {
Set<Inmate> inmateSet = new HashSet<> (50);
for (InstructorTreeNode node: getChildNodes()) {
inmateSet.addAll(node.getInmates());
}
return inmateSet;
}
public int getSortOrder() {
return sortOrder;
}
public EmployeeState getStatus() {
return status;
}
#Override
public List<InstructorTreeNode> getTree() {
List <InstructorTreeNode> result = new ArrayList<> (25);
for (InstructorTreeNode childNode: getChildNodes()) {
if (childNode instanceof FolderNode) {
result.add(childNode);
}
result.addAll(childNode.getTree());
}
return result;
}
#Override
public JPanel getView(EmployeeViewController controller) {
if ("Employee Attendance".equals(getName())) {
return new AttendanceView();
} else if ("Waiting List".equals(getName())) {
return new AllWaitingListsPanel(controller);
} else if (getParentNode().getName().equals("Waiting List")) {
return new WaitingListPanel(controller);
} else if ("Pending Students".equals(getName())) {
return new PendingStudentsPanel(controller);
} else if ("Students".equals(getName())) {
return new AllStudentsPanel(controller);
} else if ("AM Class".equals(getName())) {
return new AllStudentsPanel(controller, Shift.AM);
} else if ("PM Class".equals(getName())) {
return new AllStudentsPanel(controller, Shift.PM);
} else if (getParentNode().getName().equals("Students") && "Former".equals(getName())) {
return new FormerStudentsPanel(controller);
} else if ("Parapros".equals(getName())) {
return new AllParaprosPanel(controller);
} else if (getParentNode().getName().equals("Parapros") && "Former".equals(getName())) {
return new FormerParaprosPanel(controller);
}
throw new UnsupportedOperationException("unknown folder");
}
public void removeChild(InstructorTreeNode node) {
childNodes.remove(node);
node.setParentNode(null);
}
public void removeEmployeeLeaf(Inmate inmate) {
for (InstructorTreeNode node: childNodes) {
if (node instanceof EmployeeLeaf) {
EmployeeLeaf employeeLeaf = (EmployeeLeaf) node;
if (employeeLeaf.getInmate().equals(inmate)) {
childNodes.remove(employeeLeaf);
break;
}
}
}
}
public void setChildNodes(List<InstructorTreeNode> childNodes) {
this.childNodes = childNodes;
}
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
public void setStatus(EmployeeState status) {
this.status = status;
}
#OneToMany(mappedBy = "parentNode", cascade = CascadeType.ALL, orphanRemoval = true)
private List<InstructorTreeNode> childNodes;
private int sortOrder;
#Enumerated(EnumType.STRING)
private EmployeeState status;
}
#Entity
#Table(catalog = "education", name = "instructortreenode", uniqueConstraints = #UniqueConstraint(columnNames = {
"PARENTNODE_ID", "NAME"
}))
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class InstructorTreeNode implements Comparable<InstructorTreeNode> {
public InstructorTreeNode() {
super();
}
public static InstructorTreeNodeDAO getDao() {
return dao;
}
#Override
public int compareTo(InstructorTreeNode o) {
if (o instanceof FolderNode && this instanceof FolderNode) {
FolderNode thisFolder = (FolderNode) this;
FolderNode otherFolder = (FolderNode) o;
if (thisFolder.getSortOrder() != otherFolder.getSortOrder()) {
return thisFolder.getSortOrder() - otherFolder.getSortOrder();
} else {
return thisFolder.getName().compareToIgnoreCase(otherFolder.getName());
}
} else if (o instanceof EmployeeLeaf && this instanceof EmployeeLeaf) {
return getName().compareToIgnoreCase(((InstructorTreeNode) o).getName());
}
return (o instanceof FolderNode) ? -1 : +1;
}
public int getCount() {
return getTree().size();
}
public abstract Set<Inmate> getInmates();
public String getName() {
return name;
}
public FolderNode getParentNode() {
return parentNode;
}
public abstract List<InstructorTreeNode> getTree();
public abstract JPanel getView(EmployeeViewController theController);
public void setName(String name) {
this.name = name;
}
public void setParentNode(FolderNode parentNode) {
this.parentNode = parentNode;
}
#Override
public String toString() {
return name;
}
private static final InstructorTreeNodeDAO dao = new InstructorTreeNodeDAO();
private String name;
#ManyToOne
private FolderNode parentNode;
}
Here is my problem:
The Collections.sort line works just fine in Java 8u5 and before, but
in Java 8u20 they seem to have changed the code for Collections.sort
and it no longer uses anything but the natural order, even if you specify
a Comparator.
Should I be using another method to sort my list, or is there an error in
Collections.sort.
Any help would be much appreciated, as this is driving me crazy.
I forgot to say that this code does not use a specified comparator, but according to the documentation it is supposed to use the CompareTo, if your class implements Comparable, which is what I am using.
I tried also specifying a comparator, but it did not work either.
Since Collections.sort now delegates to List.sort, the actual List implementation has an impact. Implementations like ArrayList and Vector take the opportunity to implement List.sort in a more efficient manner than the default implementation as they pass their internal array directly to Arrays.sort omitting the copy steps of the default implementation.
This works seamlessly unless programmers use the anti-pattern of subclassing an implementation (rather than using delegation) overriding methods to implement a contradicting behavior. Lazily populated lists like these from EclipseLink/JPA are known to have problems with this as they try to intercept every reading method to populate the list before proceeding but miss the new sort method. If the list hasn’t populated yet when sort is called, sort will see an empty list state.
In your code, there is no indication where the list does come from and which actual implementation class it has, but since I see a lot of familiar looking annotations, I guess, you are using such a framework…
If you use the method Collections#sort(List<T> list), it defers to the method List#sort(Comparator comparator) with comparator given as null. The source code from java.util.Collections is as follows:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
If you want to specify your own Comparator, you need to use the method Collections#sort(List<T> list, Comparator<T> comparator), which passes on your comparator to the list sorting method. The source code from java.util.Collections is as follows:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
So far so good. Now, as you have correctly pointed out, if you do not specify a comparator, the natural ordering of the class, that is, the compareTo method you have defined, is used.
However, the Comparable class documentation also states the following:
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
Since the class InstructorTreeNode does not override Object#equals, your compareTo method may return 0 even if == returns false. I reckon this is leading to what the documentation calls "strangely".
You might not like this answer because it doesn't give you a quick-fix for your situation, but it will help you more in the long run.
This is the kind of bug that you can figure out yourself with a little debugging. I don't know what IDE you are using, but with Eclipse, you can even step into code that is in the JDK!
So, what I would do, is set a breakpoint at the line where you call sort() on the childNodes. Then I would step into the JDK code and just walk through it myself. It will become very clear what is going on and why it isn't calling your compare function.
You could try to build a custom comparator. Here is an example how that should look. This is for comparing BigDecimals.
class YourComparator implements Comparator<InstructorTreeNode> {
#Override
public int compare(final InstructorTreeNode 01, final InstructorTreeNode o2) {
return o2.getYourCompVal().compareTo(o1.getYourCompVal());
}
}
public List<InstructorTreeNode> getChildNodes() {
Collections.sort(childNodes, new YourComparator());
return childNodes;}