Java tutorial
/** * Copyright 2004-2009 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.eve.skills.db.dao; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.hibernate.Hibernate; import org.hibernate.Query; import org.hibernate.Session; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import de.codesourcery.eve.skills.datamodel.Blueprint; import de.codesourcery.eve.skills.datamodel.ItemWithQuantity; import de.codesourcery.eve.skills.datamodel.Prerequisite; import de.codesourcery.eve.skills.datamodel.RequiredMaterial; import de.codesourcery.eve.skills.datamodel.Requirements; import de.codesourcery.eve.skills.datamodel.SkillTree; import de.codesourcery.eve.skills.db.datamodel.Activity; import de.codesourcery.eve.skills.db.datamodel.BlueprintType; import de.codesourcery.eve.skills.db.datamodel.InventoryGroup; import de.codesourcery.eve.skills.db.datamodel.InventoryType; import de.codesourcery.eve.skills.db.datamodel.TypeActivityMaterials; import de.codesourcery.eve.skills.db.datamodel.TypeMaterial; import de.codesourcery.eve.skills.exceptions.NoTech1VariantException; public class BlueprintTypeDAO extends HibernateDAO<BlueprintType, Long> implements IBlueprintTypeDAO { public static final Logger log = Logger.getLogger(BlueprintTypeDAO.class); // Key is blueprint product's invTypeId private final ConcurrentHashMap<Long, Blueprint> blueprintByProductCache = new ConcurrentHashMap<>(); private ISkillTreeDAO skillTreeDAO; private ITypeActivityMaterialsDAO typeActivityMaterialsDAO; public BlueprintTypeDAO() { super(BlueprintType.class); } private BlueprintType getBlueprintTypeFor(final InventoryType type) { return execute(new HibernateCallback<BlueprintType>() { @SuppressWarnings("unchecked") @Override public BlueprintType doInSession(Session session) { final Query query = session.createQuery("from BlueprintType where productType = :type"); query.setParameter("type", type); return getExactlyOneResult((List<BlueprintType>) query.list()); } }); } @Override public Blueprint getBlueprintByProduct(InventoryType type) { Blueprint existing = blueprintByProductCache.get(type.getId()); if (existing == null) { existing = createBlueprint(getBlueprintTypeFor(type)); blueprintByProductCache.putIfAbsent(type.getId(), existing); } return existing; } public List<ItemWithQuantity> getRefiningOutcome(InventoryType item) { if (item == null) { throw new IllegalArgumentException("item cannot be NULL"); } log.debug("getRefiningOutcome(): item=" + item); List<ItemWithQuantity> result = fetchRefiningMaterials(item); log.debug("getRefiningOutcome(): Item " + item + " refines into " + result.size() + " materials."); return result; } protected MaterialRequirements fetchRequirements(final Activity activity, final BlueprintType blueprint) { if (activity == null) { throw new IllegalArgumentException("activity cannot be NULL"); } if (blueprint == null) { throw new IllegalArgumentException("blueprint cannot be NULL"); } if (activity == Activity.REFINING) { throw new RuntimeException( "Unsupported activity " + activity + " - use fetchRefiningMaterials() instead"); } final InventoryType bluePrintType = blueprint.getBlueprintType(); /* * note: TypeActivityMaterials belongs to a database view that has been removed * from the EVE DB dump and is now split across two tables , * ramTypeRequirements and invTypeMaterials. * * invTypeMaterials - holds references to all materials that are not * subject to manufacturing waste. If something has no entry here, * it cannot be produced / reprocessed. THIS TABLE USES THE PRODUCT TYPE ID (NOT THE * BLUEPRINT TYPE ID) AS INDEX. * * ramTypeRequirements - holds references to all materials that are * subject to manufacturing waste as well as all skill requirements etc. */ // fetch extra/special requirements first final List<TypeActivityMaterials> extraMaterials = execute( new HibernateCallback<List<TypeActivityMaterials>>() { @SuppressWarnings("unchecked") @Override public List<TypeActivityMaterials> doInSession(Session session) { final Query query = session.createQuery("from TypeActivityMaterials " + // SELECT ... FROM ramTypeRequirements "where typeID = :type and activityID = :activity"); query.setParameter("type", bluePrintType); query.setParameter("activity", activity, Hibernate.custom(ActivityUserType.class)); return (List<TypeActivityMaterials>) query.list(); } }); /* Add stuff from "simple materials" table * only if we're trying to manufacture a T1 item. * * Since the "simple materials" table resembles * reprocessing data as well, ignore it when * manufacturing items with tech level >1 since * they will yield the mats used for production * of the corresponding T1 items which is WRONG. */ List<TypeActivityMaterials> simpleRequirements = new ArrayList<>(); if (activity == Activity.MANUFACTURING) { simpleRequirements = getSimpleRequirements(blueprint, extraMaterials); } return new MaterialRequirements(simpleRequirements, extraMaterials); } protected static final class MaterialRequirements implements Iterable<TypeActivityMaterials> { public final List<TypeActivityMaterials> rawMats; public final List<TypeActivityMaterials> extraMats; public MaterialRequirements(List<TypeActivityMaterials> simpleMats, List<TypeActivityMaterials> extraMats) { this.rawMats = simpleMats; this.extraMats = extraMats; } public boolean isSimpleMaterial(TypeActivityMaterials mat) { return contains(rawMats, mat); } public boolean isExtraMaterial(TypeActivityMaterials mat) { return contains(extraMats, mat); } private static boolean contains(List<TypeActivityMaterials> list, TypeActivityMaterials mat) { for (TypeActivityMaterials candidate : list) { if (candidate.getRequiredType().equals(mat.getRequiredType()) && mat.getActivity().equals(candidate.getActivity())) { return true; } } return false; } @Override public Iterator<TypeActivityMaterials> iterator() { final List<TypeActivityMaterials> all = new ArrayList<>(this.rawMats); all.addAll(extraMats); return all.iterator(); } } private List<TypeActivityMaterials> getSimpleRequirements(BlueprintType blueprint, List<TypeActivityMaterials> extraMaterials) { /* * While Jercy's method looks fine to me for T1 manufacturing, it seems * to be slightly more complicated for T2. * * For example, looking at the Tritanium requirement to build 1 unit of * Medium Shield Transporter II. * * invTypeMaterials will give you a figure of 1660 tritanium. However, * the actual job quote asks for just 336 Tritanium. * * The difference is due to the requirement for the T1 module. Looking * up the T1 version in invTypeMaterials indicates that the T1 module * requires 1355 tritanium. * * 1660-1355 = 305. Adding the 10% ME wastage on top gets you to the 336 * Tritanium requested by the quote. * * The key to this is the recycle field in ramTypeRequirements. This * field is 1 for the T1 module requirement, but 0 for everything else. * Which gives us the differentiation as to why we have to remove the * Tritanium in the T1 module, but not that in the R.A.M. - Shield Tech. * * So it looks like we actually need three elements to make up the full * manufacturing requirement: * * Step (1): Records from ramTypeRequirements for activityID=1,typeID=blueprintTypeID * Step (2): Records from invTypeMaterials for typeID=productTypeID * Step (3): Records from invTypeMaterials for typeID=requiredTypeID from (2) where recycle=1 * * ( (2)-(3) )*wasteFactor then becomes your Raw Materials * (1) becomes your Extra Materials and Skills (differentiated by the categoryID of the requiredTypeID) */ final Map<InventoryType, TypeActivityMaterials> allMaterials = new HashMap<>(); // step (2) final List<ItemWithQuantity> simpleMaterials = fetchRefiningMaterials(blueprint.getProductType()); for (ItemWithQuantity mat : simpleMaterials) { final TypeActivityMaterials simpleMaterial = toTypeActivityMaterial(blueprint, mat); // all materials from this table // are subject to manufacturing waste (BPM waste,skill waste and station standings waste) simpleMaterial.setSubjectToManufacturingWaste(true); allMaterials.put(simpleMaterial.getRequiredType(), simpleMaterial); } // since the invTypeMaterials always holds the // total production/reprocessing amount for a given item, we // need to subtract the materials required for // producing any recycleable 'special' materials of the item for (TypeActivityMaterials specialMaterial : extraMaterials) { if (!specialMaterial.isRecycle()) { continue; } final List<ItemWithQuantity> t1Parts = fetchRefiningMaterials(specialMaterial.getRequiredType()); for (ItemWithQuantity mat : t1Parts) { final TypeActivityMaterials existing = allMaterials.get(mat.getType()); if (existing != null) { existing.setQuantity(existing.getQuantity() - mat.getQuantity()); } } } // remove everything with a negative quantity here final Iterator<Map.Entry<InventoryType, TypeActivityMaterials>> it = allMaterials.entrySet().iterator(); while (it.hasNext()) { final TypeActivityMaterials mat = it.next().getValue(); if (mat.getQuantity() <= 0) { it.remove(); } } return new ArrayList<>(allMaterials.values()); } private TypeActivityMaterials toTypeActivityMaterial(BlueprintType blueprint, ItemWithQuantity mat) { final TypeActivityMaterials simpleMaterial = new TypeActivityMaterials(); simpleMaterial.setActivity(Activity.MANUFACTURING); simpleMaterial.setQuantity(mat.getQuantity()); simpleMaterial.setType(blueprint.getProductType()); simpleMaterial.setRequiredType(mat.getType()); return simpleMaterial; } private List<ItemWithQuantity> fetchRefiningMaterials(final InventoryType item) { final List<TypeMaterial> materials = execute(new HibernateCallback<List<TypeMaterial>>() { @SuppressWarnings("unchecked") @Override public List<TypeMaterial> doInSession(Session session) { // query invTypeMaterials table final Query query = session.createQuery("from TypeMaterial " + "where typeID = :type"); query.setParameter("type", item); return (List<TypeMaterial>) query.list(); } }); final List<ItemWithQuantity> result = new ArrayList<ItemWithQuantity>(); for (TypeMaterial m : materials) { result.add(new ItemWithQuantity(m.getType(), m.getQuantity())); } return result; } protected Requirements createRequirements(Activity activity, BlueprintType blueprint) { final MaterialRequirements requirementsFromDB = fetchRequirements(activity, blueprint); final Requirements requirements = new Requirements(activity); System.out.println("---- Simple mats required for " + blueprint); System.out.println(StringUtils.join(requirementsFromDB.rawMats, "\n")); System.out.println("---- Extra mats required for " + blueprint); System.out.println(StringUtils.join(requirementsFromDB.extraMats, "\n")); /* * Process raw materials (taken from invTypeMaterials table) */ for (TypeActivityMaterials material : requirementsFromDB.rawMats) { if (material.getRequiredType().isSkill()) { final Prerequisite r = toSkillPrerequisite(material); requirements.addRequiredSkill(r); } else { final RequiredMaterial mat = new RequiredMaterial(material.getRequiredType(), material.getQuantity()); if (activity == Activity.MANUFACTURING) { // ignore bogus data in dump if (material.getQuantity() <= 0) { continue; } if (material.isSubjectToManufacturingWaste() || isSubjectToManufacturingWaste(blueprint, material)) { mat.setSubjectToBPMWaste(true); mat.setSubjectToSkillWaste(true); mat.setSubjectToStationWaste(true); } else { mat.setSubjectToBPMWaste(false); mat.setSubjectToSkillWaste(false); mat.setSubjectToStationWaste(false); } } mat.setDamagePerJob(material.getDamagePerJob()); mat.setSupportsRecycling(material.isRecycle()); requirements.addRequiredMaterial(mat); } } /* * Process extra materials (taken from ramTypeActivities table) */ for (TypeActivityMaterials material : requirementsFromDB.extraMats) { if (material.getRequiredType().isSkill()) { final Prerequisite r = toSkillPrerequisite(material); requirements.addRequiredSkill(r); } else { final RequiredMaterial mat = new RequiredMaterial(material.getRequiredType(), material.getQuantity()); if (activity == Activity.MANUFACTURING) { // ignore bogus data in dump if (material.getQuantity() <= 0) { continue; } /* * Special case since Odyssey: Extra materials also present * in the simple material list are subject to PE waste _ONLY_ */ if (requirementsFromDB.isSimpleMaterial(material)) { mat.setSubjectToBPMWaste(false); mat.setSubjectToSkillWaste(true); mat.setSubjectToStationWaste(false); } else { mat.setSubjectToBPMWaste(false); mat.setSubjectToSkillWaste(false); mat.setSubjectToStationWaste(false); } } mat.setDamagePerJob(material.getDamagePerJob()); mat.setSupportsRecycling(material.isRecycle()); requirements.addRequiredMaterial(mat); } } return requirements; } private Prerequisite toSkillPrerequisite(TypeActivityMaterials requirement) { final Prerequisite r = new Prerequisite(); final int lvl = requirement.getQuantity(); if (lvl <= 0) { throw new RuntimeException("Requirement " + requirement + " with skill lvl <= 0 ?"); } r.setRequiredLevel(lvl); r.setSkill(getSkillTree().getSkill(requirement.getRequiredType().getId().intValue())); return r; } /* QUESTION: So where exactly in the database dump DO you find the attribute that says wether a material is "raw" or "extra" for each specific blueprint (or item) ? ANSWER: An attribute does not exist but you can determine whether a material is raw or extra by examining table typeActivityMaterials in the data dump. The data we need to look at is activityID 1 for the blueprintTypeID and activityID 6 for the productTypeID that the blueprint in question produces. a.) A material is raw if the activity 6 qty is greater than or equal to the activty 1 qty. b.) A material is extra if activity 6 does not exist for the activity 1 typeID. Ignore any activity 6 typeIDs that don't have an activity 1 counterpart. */ protected boolean isSubjectToManufacturingWaste(final BlueprintType blueprint, final TypeActivityMaterials requirement) { // select quantity for activity 6 of the product // the BP produces and of the material in question final List<TypeActivityMaterials> typeActivityForProduct = execute( new HibernateCallback<List<TypeActivityMaterials>>() { @SuppressWarnings("unchecked") @Override public List<TypeActivityMaterials> doInSession(Session session) { final Query query = session.createQuery("from TypeActivityMaterials " + "where typeID = :type and activityID = :activity and" + " requiredTypeID = :requiredType"); query.setParameter("type", blueprint.getProductType()); query.setParameter("requiredType", requirement.getRequiredType()); query.setParameter("activity", Activity.REFINING, Hibernate.custom(ActivityUserType.class)); return (List<TypeActivityMaterials>) query.list(); } }); if (typeActivityForProduct.isEmpty()) { return false; } if (typeActivityForProduct.size() != 1) { throw new RuntimeException("Internal error for " + requirement.getType() + ", expected exactly one result but got " + typeActivityForProduct); } final TypeActivityMaterials mat = typeActivityForProduct.get(0); if (mat.getQuantity() >= requirement.getQuantity()) { return true; } return false; } protected Map<Activity, Requirements> fetchRequirements(final BlueprintType blueprint) { final Map<Activity, Requirements> result = new HashMap<Activity, Requirements>(); result.put(Activity.MANUFACTURING, createRequirements(Activity.MANUFACTURING, blueprint)); if (blueprint.getTechLevel() == 1) { // only Tech1 BPs can be used for invention result.put(Activity.INVENTION, createRequirements(Activity.INVENTION, blueprint)); } return result; } @Override public List<Blueprint> getBlueprintsByProductName(final String name) { final List<BlueprintType> types = execute(new HibernateCallback<List<BlueprintType>>() { @SuppressWarnings("unchecked") @Override public List<BlueprintType> doInSession(Session session) { final Query query = session .createQuery("select b from " + "BlueprintType b inner join InventoryType t " + "on b.productType = t where t.typeName like :name"); query.setParameter("name", name); return (List<BlueprintType>) query.list(); } }); final List<Blueprint> result = new ArrayList<Blueprint>(); for (BlueprintType print : types) { result.add(createBlueprint(print)); } return result; } @Override public Blueprint getBlueprint(final InventoryType blueprint) { final BlueprintType type = execute(new HibernateCallback<BlueprintType>() { @SuppressWarnings("unchecked") @Override public BlueprintType doInSession(Session session) { final Query query = session.createQuery("from BlueprintType where blueprintTypeID = :type"); query.setParameter("type", blueprint.getId()); try { return getExactlyOneResult((List<BlueprintType>) query.list()); } catch (EmptyResultDataAccessException e) { log.error("failed to locate blueprint " + blueprint); throw e; } } }); return createBlueprint(type); } @Override public List<Blueprint> getBlueprintsByProductGroup(final InventoryGroup group) { return execute(new HibernateCallback<List<Blueprint>>() { @SuppressWarnings("unchecked") @Override public List<Blueprint> doInSession(Session session) { final Query query = session.createQuery(" select b from " + "BlueprintType b , InventoryType t " + " where b.productType = t.typeId and t.groupId = :group"); query.setParameter("group", group); final List<BlueprintType> types = (List<BlueprintType>) query.list(); final List<Blueprint> result = new ArrayList<Blueprint>(); for (BlueprintType type : types) { result.add(createBlueprint(type)); } return result; } }); } protected Blueprint createBlueprint(final BlueprintType type) { return new Blueprint(type) { @Override protected Map<Activity, Requirements> fetchRequirements() { return BlueprintTypeDAO.this.fetchRequirements(type); } }; } protected SkillTree getSkillTree() { return skillTreeDAO.getSkillTree(); } public void setSkillTreeDAO(ISkillTreeDAO skillTreeDAO) { this.skillTreeDAO = skillTreeDAO; } @Override public Blueprint getBlueprintByName(final String name) { return execute(new HibernateCallback<Blueprint>() { @SuppressWarnings("unchecked") @Override public Blueprint doInSession(Session session) { final Query query = session.createQuery(" select b from " + "BlueprintType b , InventoryType t " + " where b.blueprintType = t.typeId and t.name = :blueprintName"); query.setParameter("blueprintName", name); final List<BlueprintType> types = (List<BlueprintType>) query.list(); if (types.size() == 1) { return createBlueprint(types.get(0)); } throw new IncorrectResultSizeDataAccessException("Expected one blueprint with name '" + name + "'", 1, types.size()); } }); } @Override public List<Blueprint> getTech2Variations(final Blueprint blueprint) { if (blueprint.getTechLevel() != 1) { throw new IllegalArgumentException("This method requires a Tech1 blueprint"); } /* * [...] you take the invBlueprintTypes.productTypeID field for the * tech 1 blueprint and look it up in invMetaTypes.parentTypeID * with invMetaTypes.metaGroup='2' * that will give you the tech 2 items that can be invented. */ return execute(new HibernateCallback<List<Blueprint>>() { @SuppressWarnings("unchecked") @Override public List<Blueprint> doInSession(Session session) { final Query query = session.createQuery(" select b from " + "BlueprintType b , InventoryMetaType g " + " where g.metaGroupId = 2 and g.parentType = :product " + " and b.productType = g.id"); query.setParameter("product", blueprint.getType().getProductType()); final List<BlueprintType> types = (List<BlueprintType>) query.list(); final List<Blueprint> result = new ArrayList<Blueprint>(); for (BlueprintType type : types) { result.add(createBlueprint(type)); } return result; } }); } @Override public Blueprint getTech1Variation(final Blueprint tech2Blueprint) throws NoTech1VariantException { return getTech1Variation(tech2Blueprint.getType()); } protected Blueprint getTech1Variation(final BlueprintType tech2Blueprint) throws NoTech1VariantException { if (tech2Blueprint.getTechLevel() != 2) { throw new IllegalArgumentException("This method requires a Tech2 blueprint"); } /* * [...] you take the invBlueprintTypes.productTypeID field for the * tech 1 blueprint and look it up in invMetaTypes.parentTypeID * with invMetaTypes.metaGroup='2' * that will give you the tech 2 items that can be invented. */ return execute(new HibernateCallback<Blueprint>() { @SuppressWarnings("unchecked") @Override public Blueprint doInSession(Session session) { final Query query = session.createSQLQuery( "select b.* from " + "invBlueprintTypes b , invMetaTypes g " + " where g.typeID = " + tech2Blueprint.getProductType().getId() + " and b.productTypeID = g.parentTypeID") .addEntity(BlueprintType.class); final List<BlueprintType> types = (List<BlueprintType>) query.list(); if (types.isEmpty()) { throw new NoTech1VariantException(tech2Blueprint); } else if (types.size() > 1) { throw new IncorrectResultSizeDataAccessException( "Unable to find Tech1 variant of " + tech2Blueprint.getBlueprintType(), 1, types.size()); } return createBlueprint(types.get(0)); } }); } }