/
com/planet_ink/coffee_mud/Abilities/Common/
com/planet_ink/coffee_mud/Abilities/Diseases/
com/planet_ink/coffee_mud/Abilities/Druid/
com/planet_ink/coffee_mud/Abilities/Fighter/
com/planet_ink/coffee_mud/Abilities/Languages/
com/planet_ink/coffee_mud/Abilities/Misc/
com/planet_ink/coffee_mud/Abilities/Prayers/
com/planet_ink/coffee_mud/Abilities/Properties/
com/planet_ink/coffee_mud/Abilities/Skills/
com/planet_ink/coffee_mud/Abilities/Songs/
com/planet_ink/coffee_mud/Abilities/Specializations/
com/planet_ink/coffee_mud/Abilities/Spells/
com/planet_ink/coffee_mud/Abilities/Thief/
com/planet_ink/coffee_mud/Abilities/Traps/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/CharClasses/
com/planet_ink/coffee_mud/CharClasses/interfaces/
com/planet_ink/coffee_mud/Commands/
com/planet_ink/coffee_mud/Commands/interfaces/
com/planet_ink/coffee_mud/Common/
com/planet_ink/coffee_mud/Common/interfaces/
com/planet_ink/coffee_mud/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/BasicTech/
com/planet_ink/coffee_mud/Items/CompTech/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Items/interfaces/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/MOBS/
com/planet_ink/coffee_mud/Races/
com/planet_ink/coffee_mud/Races/interfaces/
com/planet_ink/coffee_mud/WebMacros/
com/planet_ink/coffee_mud/WebMacros/interfaces/
com/planet_ink/coffee_mud/core/
com/planet_ink/coffee_mud/core/collections/
com/planet_ink/coffee_mud/core/interfaces/
com/planet_ink/coffee_mud/core/intermud/
com/planet_ink/coffee_mud/core/intermud/i3/
com/planet_ink/coffee_web/server/
com/planet_ink/siplet/applet/
lib/
resources/factions/
resources/fakedb/
resources/progs/autoplayer/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/clan.templates/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
web/pub/textedit/
package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.CatalogLibrary.CataData;
import com.planet_ink.coffee_mud.Libraries.interfaces.CatalogLibrary.RoomContent;
import com.planet_ink.coffee_mud.Libraries.interfaces.DatabaseEngine.PlayerData;
import com.planet_ink.coffee_mud.Libraries.interfaces.XMLLibrary.XMLTag;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
/*
   Copyright 2008-2019 Bo Zimmerman

   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.
*/
public class CMCatalog extends StdLibrary implements CatalogLibrary
{
	@Override
	public String ID()
	{
		return "CMCatalog";
	}

	protected String				commonBuilderTemplateKey= "SYSTEM_BTEMPLATE";
	protected String				templatePersonalSection	= commonBuilderTemplateKey + "_PERSONAL";
	protected String				templateSharedSection	= commonBuilderTemplateKey + "_SHARED";

	public DVector					icatalog				= new DVector(2);
	public DVector					mcatalog				= new DVector(2);
	public volatile CMFile.CMVFSDir	catalogFileMobsRoot		= null;
	public volatile CMFile.CMVFSDir	catalogFileItemsRoot	= null;

	public void changeCatalogFlag(final Physical P, final boolean truefalse)
	{
		if(P==null)
			return;
		if(CMath.bset(P.basePhyStats().disposition(),PhyStats.IS_CATALOGED))
		{
			if(!truefalse)
			{
				P.basePhyStats().setDisposition(CMath.unsetb(P.basePhyStats().disposition(),PhyStats.IS_CATALOGED));
				P.phyStats().setDisposition(CMath.unsetb(P.phyStats().disposition(),PhyStats.IS_CATALOGED));
			}
		}
		else
		if(truefalse)
		{
			P.basePhyStats().setDisposition(CMath.setb(P.basePhyStats().disposition(),PhyStats.IS_CATALOGED));
			P.phyStats().setDisposition(CMath.setb(P.phyStats().disposition(),PhyStats.IS_CATALOGED));
		}
	}

	protected Object getCatalogObject(final DVector list, final String name, final int dim)
	{
		synchronized(list)
		{
			try
			{
				if(list.size()==0)
					return null;
				int start=0;
				int end=list.size()-1;
				while(start<=end)
				{
					final int mid=(end+start)/2;
					final int comp=((Environmental)list.elementAt(mid,1)).Name().compareToIgnoreCase(name);
					if(comp==0)
						return list.elementAt(mid,dim);
					else
					if(comp>0)
						end=mid-1;
					else
						start=mid+1;

				}
			}
			catch(final Exception e)
			{
			}
			return null;
		}
	}

	protected void addCatalogReplace(final DVector DV, final String category, final Physical P)
	{
		int start=0;
		int end=DV.size()-1;
		final String name=P.Name();
		int lastStart=0;
		int lastEnd=DV.size()-1;
		int comp=-1;
		int mid=-1;
		while(start<=end)
		{
			mid=(end+start)/2;
			comp=((Environmental)DV.elementAt(mid,1)).Name().compareToIgnoreCase(name);
			if(comp==0)
				break;
			else
			if(comp>0)
			{
				lastEnd=end;
				end=mid-1;
			}
			else
			{
				lastStart=start;
				start=mid+1;
			}
		}
		if(comp==0)
		{
			if((P instanceof DBIdentifiable)
			&&((DBIdentifiable)DV.elementAt(mid,1)).databaseID().length()>0)
				((DBIdentifiable)P).setDatabaseID(((DBIdentifiable)DV.elementAt(mid,1)).databaseID());
			((Environmental)DV.elementAt(mid,1)).destroy();
			DV.setElementAt(mid,1,P);
		}
		else
		{
			final CataData data=new CataDataImpl("");
			if(category!=null)
				data.setCategory(category);
			if(mid>=0)
			{
				for(comp=lastStart;comp<=lastEnd;comp++)
				{
					if(((Environmental)DV.elementAt(comp,1)).Name().compareToIgnoreCase(name)>0)
					{
						DV.insertElementAt(comp,P,data);
						return;
					}
				}
			}
			DV.addElement(P,data);
		}
	}

	public String[] makeCatalogNames(final String catName, final DVector catalog)
	{
		final List<String> nameList=new ArrayList<String>(catalog.size());
		for(int x=0;x<catalog.size();x++)
		{
			if((catName==null)||(catName.equals(((CataData)catalog.elementAt(x, 2)).category())))
				nameList.add(((Environmental)catalog.elementAt(x, 1)).Name());
		}
		return nameList.toArray(new String[0]);
	}

	public String[] makeCatalogCatagories(final DVector catalog)
	{
		final List<String> catalogList=new SortedListWrap<String>(new ArrayList<String>(2));
		for(int x=0;x<catalog.size();x++)
		{
			if(!catalogList.contains(((CataData)catalog.elementAt(x, 2)).category()))
				catalogList.add(((CataData)catalog.elementAt(x, 2)).category());
		}
		return catalogList.toArray(new String[catalogList.size()]);
	}

	@Override
	public String[] getCatalogItemNames()
	{
		return makeCatalogNames(null, icatalog);
	}

	@Override
	public String[] getCatalogItemNames(final String cataName)
	{
		return makeCatalogNames(cataName, icatalog);
	}

	@Override
	public String[] getCatalogMobNames()
	{
		return makeCatalogNames(null, mcatalog);
	}

	@Override
	public String[] getCatalogMobNames(final String cataName)
	{
		return makeCatalogNames(cataName, mcatalog);
	}

	@Override
	public String[] getMobCatalogCatagories()
	{
		return makeCatalogCatagories(mcatalog);
	}

	@Override
	public String[] getItemCatalogCatagories()
	{
		return makeCatalogCatagories(icatalog);
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Item[] getCatalogItems()
	{
		final List<Item> itemsV=(List)icatalog.getDimensionList(1);
		return itemsV.toArray(new Item[itemsV.size()]);
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public MOB[] getCatalogMobs()
	{
		final List<MOB> mobsV=(List)mcatalog.getDimensionList(1);
		return mobsV.toArray(new MOB[mobsV.size()]);
	}

	@Override
	public boolean isCatalogObj(final Environmental E)
	{
		if(E instanceof MOB)
			return getCatalogMob(E.Name()) != null;
		if(E instanceof Item)
			return getCatalogItem(E.Name()) != null;
		return false;
	}

	@Override
	public boolean isCatalogObj(final String name)
	{
		Object o=getCatalogMob(name);
		if(o==null)
			o=getCatalogItem(name);
		return o!=null;
	}

	@Override
	public Item getCatalogItem(final String called)
	{
		return (Item) getCatalogObject(icatalog, called, 1);
	}

	@Override
	public MOB getCatalogMob(final String called)
	{
		return (MOB) getCatalogObject(mcatalog, called, 1);
	}

	@Override
	public CataData getCatalogItemData(final String called)
	{
		return (CataData) getCatalogObject(icatalog, called, 2);
	}

	@Override
	public CataData getCatalogMobData(final String called)
	{
		return (CataData) getCatalogObject(mcatalog, called, 2);
	}

	@Override
	public Vector<RoomContent> roomContent(final Room R)
	{
		Item I=null;
		MOB M=null;
		Environmental E=null;
		ShopKeeper SK=null;
		List<Environmental> shops=null;
		Environmental shopItem=null;
		final Vector<RoomContent> content =new Vector<RoomContent>();
		if(R!=null)
		{
			shops=CMLib.coffeeShops().getAllShopkeepers(R,null);
			for(int s=0;s<shops.size();s++)
			{
				E=shops.get(s);
				if(E==null)
					continue;
				SK=CMLib.coffeeShops().getShopKeeper(E);
				if(SK==null)
					continue;
				for(final Iterator<Environmental> i=SK.getShop().getStoreInventory();i.hasNext();)
				{
					shopItem=i.next();
					if(shopItem instanceof Physical)
						content.addElement(new RoomContentImpl((Physical)shopItem,SK));
				}
			}
			for(int i=0;i<R.numItems();i++)
			{
				I=R.getItem(i);
				if(I!=null)
					content.addElement(new RoomContentImpl(I));
			}
			for(int m=0;m<R.numInhabitants();m++)
			{
				M=R.fetchInhabitant(m);
				if(M==null)
					continue;
				for(int i=0;i<M.numItems();i++)
				{
					I=M.getItem(i);
					if(I!=null)
						content.addElement(new RoomContentImpl(I,M));
				}
				content.addElement(new RoomContentImpl(M));
			}
		}
		return content;
	}

	@Override
	public void updateRoomContent(final String roomID, final List<RoomContent> content)
	{
		final List<Environmental> updatables=new LinkedList<Environmental>();
		final List<Environmental> deletables=new LinkedList<Environmental>();
		for(final RoomContent C : content)
		{
			if(C.deleted())
			{
				if(C.holder()!=null)
				{
					if(!updatables.contains(C.holder()))
						updatables.add(C.holder());
				}
				else
				if(!updatables.contains(C.P()))
					deletables.add(C.P());
			}
			else
			if(C.isDirty())
			{
				if(C.holder()!=null)
				{
					if(!updatables.contains(C.holder()))
						updatables.add(C.holder());
				}
				else
				if(!updatables.contains(C.P()))
					updatables.add(C.P());
			}
		}
		for(final Environmental E : deletables)
		{
			updatables.remove(E);
			if((!(E instanceof DBIdentifiable))
			||(!((DBIdentifiable)E).canSaveDatabaseID())
			||(((DBIdentifiable)E).databaseID().trim().length()==0))
				continue;
			if(E instanceof MOB)
			{
				CMLib.database().DBDeleteMOB(roomID,(MOB)E);
				this.catalogFileMobsRoot=null;
			}
			else
			{
				CMLib.database().DBDeleteItem(roomID,(Item)E);
				this.catalogFileItemsRoot=null;
			}
		}
		for(final Environmental E : updatables)
		{
			if(E instanceof ShopKeeper)
			{
				final ShopKeeper SK=(ShopKeeper)E;
				final Vector<Environmental> newShop=new Vector<Environmental>();
				for(final RoomContent C : content)
				{
					if((C.holder()==SK)&&(!C.deleted()))
						newShop.addElement(C.P());
				}
				final CoffeeShop shop=(SK instanceof Librarian)?((Librarian)SK).getBaseLibrary():SK.getShop();
				shop.resubmitInventory(newShop);
			}
		}
		for(final Environmental E : updatables)
		{
			if((!(E instanceof DBIdentifiable))
			||(!((DBIdentifiable)E).canSaveDatabaseID())
			||(((DBIdentifiable)E).databaseID().trim().length()==0))
				continue;
			if(E instanceof MOB)
			{
				CMLib.database().DBUpdateMOB(roomID,(MOB)E);
				this.catalogFileMobsRoot=null;
			}
			else
			{
				CMLib.database().DBUpdateItem(roomID,(Item)E);
				this.catalogFileItemsRoot=null;
			}
		}
	}

	@Override
	public void addCatalog(final Physical PA)
	{
		addCatalog(null,PA);
	}

	@Override
	public void addCatalog(final String category, Physical PA)
	{
		if((PA==null)
		||(!(PA instanceof DBIdentifiable))
		||(!((DBIdentifiable)PA).canSaveDatabaseID()))
			return;
		synchronized(getSync(PA).intern())
		{
			changeCatalogFlag(PA,true);
			final Physical origP=PA;
			PA=(Physical)origP.copyOf();
			submitToCatalog(PA);
			if(PA instanceof Item)
			{
				CMLib.database().DBCreateThisItem("CATALOG_ITEMS",(Item)PA);
				this.catalogFileItemsRoot=null;
			}
			else
			if(PA instanceof MOB)
			{
				CMLib.database().DBCreateThisMOB("CATALOG_MOBS",(MOB)PA);
				this.catalogFileMobsRoot=null;
			}
			final CataData data=getCatalogData(PA);
			if(data!=null)
			{
				if(category != null)
					data.setCategory(category);
				data.addReference(origP);
			}
		}
	}

	@Override
	public void submitToCatalog(final Physical P)
	{
		submitToCatalog(null,P);
	}

	public void submitToCatalog(final String category, final Physical P)
	{
		if((P==null)
		||(!(P instanceof DBIdentifiable))
		||(!((DBIdentifiable)P).canSaveDatabaseID()))
			return;
		CMLib.threads().deleteAllTicks(P);
		synchronized(getSync(P).intern())
		{
			if(getCatalogObj(P)!=null)
				return;
			changeCatalogFlag(P,false);
			P.text(); // to get cataloged status into xml
			if(P instanceof Item)
			{
				synchronized(icatalog)
				{
					addCatalogReplace(icatalog,category,P);
				}
			}
			else
			if(P instanceof MOB)
			{
				synchronized(mcatalog)
				{
					addCatalogReplace(mcatalog,category,P);
				}
			}
		}
	}

	@Override
	public void delCatalog(Physical P)
	{
		if(P==null)
			return;
		P=getCatalogObj(P);
		if(P==null)
			return;
		final CataData data=getCatalogData(P);
		if(P instanceof Item)
		{
			synchronized(icatalog)
			{
				icatalog.removeElement(P);
			}
			CMLib.database().DBDeleteItem("CATALOG_ITEMS",(Item)P);
			this.catalogFileItemsRoot=null;
		}
		else
		if(P instanceof MOB)
		{
			synchronized(mcatalog)
			{
				mcatalog.removeElement(P);
			}
			CMLib.database().DBDeleteMOB("CATALOG_MOBS",(MOB)P);
			this.catalogFileMobsRoot=null;
		}
		if(data!=null)
		{
			final ArrayList<Room> rooms=new ArrayList<Room>();
			for(final Enumeration<Physical> e=data.enumeration();e.hasMoreElements();)
			{
				final Physical P2=e.nextElement();
				final Room R=CMLib.map().getStartRoom(P2);
				if((R!=null)&&(!rooms.contains(R)))
					rooms.add(R);
				changeCatalogUsage(P2,false);
			}
			for(Room R : rooms)
			{
				List<RoomContent> contents=roomContent(R);
				for(final RoomContent content : contents)
				{
					if((CMLib.flags().isCataloged(content.P()))
					&&(content.P().Name().equalsIgnoreCase(P.Name())))
					{
						if(((P instanceof MOB)&&(content.P() instanceof MOB))
						||((P instanceof Item)&&(content.P() instanceof Item)))
							changeCatalogFlag(content.P(),false);
					}
				}
				R=CMLib.coffeeMaker().makeNewRoomContent(R,false);
				contents=roomContent(R);
				boolean dirty=false;
				for(final RoomContent content : contents)
				{
					if((CMLib.flags().isCataloged(content.P()))
					&&(content.P().Name().equalsIgnoreCase(P.Name())))
					{
						if(((P instanceof MOB)&&(content.P() instanceof MOB))
						||((P instanceof Item)&&(content.P() instanceof Item)))
						{
							changeCatalogFlag(content.P(),false);
							content.flagDirty();
							dirty=true;
						}
					}
				}
				if(dirty)
					updateRoomContent(R.roomID(),contents);
				R.destroy();
			}
		}
	}

	@Override
	public void updateCatalogCategory(final Physical modelP, final String newCat)
	{
		if((modelP==null)
		||(!(modelP instanceof DBIdentifiable))
		||(!((DBIdentifiable)modelP).canSaveDatabaseID()))
			return;
		synchronized(getSync(modelP).intern())
		{
			final CataData data=getCatalogData(modelP);
			if(data!=null)
			{
				data.setCategory(newCat.toUpperCase().trim());
				if(modelP instanceof MOB)
				{
					CMLib.database().DBUpdateMOB("CATALOG_MOBS",(MOB)modelP);
					this.catalogFileMobsRoot=null;
				}
				else
				{
					CMLib.database().DBUpdateItem("CATALOG_ITEMS",(Item)modelP);
					this.catalogFileItemsRoot=null;
				}
			}
		}

	}

	@Override
	public void updateCatalog(final Physical modelP)
	{
		if((modelP==null)
		||(!(modelP instanceof DBIdentifiable))
		||(!((DBIdentifiable)modelP).canSaveDatabaseID()))
			return;
		synchronized(getSync(modelP).intern())
		{
			changeCatalogFlag(modelP,false);
			Physical cataP=(Physical)modelP.copyOf();
			cataP.text(); // to get cataloged status into xml
			if(modelP!=getCatalogObj(modelP))
				changeCatalogFlag(modelP,true);
			if(cataP instanceof Item)
			{
				synchronized(icatalog)
				{
					addCatalogReplace(icatalog,null,cataP);
				}
			}
			else
			if(cataP instanceof MOB)
			{
				synchronized(mcatalog)
				{
					addCatalogReplace(mcatalog,null,cataP);
				}
			}
			cataP=getCatalogObj(cataP);
			if(cataP instanceof MOB)
			{
				CMLib.database().DBUpdateMOB("CATALOG_MOBS",(MOB)cataP);
				this.catalogFileMobsRoot=null;
			}
			else
			{
				CMLib.database().DBUpdateItem("CATALOG_ITEMS",(Item)cataP);
				this.catalogFileItemsRoot=null;
			}

			final CataData data = getCatalogData(cataP);
			if(data!=null)
				data.delReference(cataP);
			SHashSet<Physical> ignored=null;
			if(data!=null)
				ignored=new SHashSet<Physical>(data.enumeration());
			else
				ignored=new SHashSet<Physical>(1);
			Physical P;
			for(final Iterator<Physical> i=ignored.iterator();i.hasNext();)
			{
				P=i.next();
				if((!P.amDestroyed())
				&&(CMLib.flags().isCataloged(P))
				&&(cataP!=P)
				&&(P.Name().equalsIgnoreCase(cataP.Name())))
				{
					P.setMiscText(P.text());
					changeCatalogFlag(P,true);
				}
			}
			final Vector<Environmental> all=new Vector<Environmental>();
			final String srchStr="$"+cataP.Name()+"$";
			final boolean isMob=(cataP instanceof MOB);
			if(cataP instanceof MOB)
			{
				all.addAll(CMLib.map().findInhabitants(CMLib.map().rooms(),null, srchStr, 50));
				all.addAll(CMLib.map().findShopStock(CMLib.map().rooms(),null, srchStr, 50));
			}
			else
			{
				all.addAll(CMLib.map().findRoomItems(CMLib.map().rooms(),null, srchStr, true, 50));
				all.addAll(CMLib.map().findInventory(CMLib.map().rooms(),null, srchStr, 50));
				all.addAll(CMLib.map().findInventory(null,null, srchStr, 50));
				all.addAll(CMLib.map().findShopStockers(CMLib.map().rooms(),null, srchStr, 50));
				all.addAll(CMLib.map().findShopStockers(null,null, srchStr, 50));
			}
			final HashSet<ShopKeeper> doneShops=new HashSet<ShopKeeper>();
			ShopKeeper SK=null;
			for (final Environmental environmental : all)
			{
				P=(Physical)environmental;
				if((CMLib.flags().isCataloged(P))
				&&(!ignored.contains(P))
				&&(((isMob)&&(P instanceof MOB))||((!isMob)&&(P instanceof Item)))
				&&(cataP.Name().equalsIgnoreCase(P.Name())))
				{
					ignored.add(P);
					P.setMiscText(P.text());
					changeCatalogFlag(P,true);
				}
				SK=CMLib.coffeeShops().getShopKeeper(P);
				if((SK!=null)&&(!doneShops.contains(SK)))
				{
					doneShops.add(SK);
					propogateShopChange(SK,ignored,cataP);
				}
			}
		}
	}

	@Override
	public void newInstance(final Physical P)
	{
		synchronized(getSync(P).intern())
		{
			final PhyStats stats=P.basePhyStats();
			if((stats!=null)&&(CMath.bset(stats.disposition(),PhyStats.IS_CATALOGED)))
			{
				final CataData data=getCatalogData(P);
				if(data!=null)
					data.addReference(P);
			}
		}
	}

	@Override
	public void bumpDeathPickup(final Physical P)
	{
		synchronized(getSync(P).intern())
		{
			final PhyStats stats=P.basePhyStats();
			if((stats!=null)&&(CMath.bset(stats.disposition(),PhyStats.IS_CATALOGED)))
			{
				final CataData data=getCatalogData(P);
				if(data!=null)
					data.bumpDeathPickup();
			}
		}
	}

	@Override
	public void changeCatalogUsage(final Physical P, final boolean toCataloged)
	{
		synchronized(getSync(P).intern())
		{
			if((P!=null)
			&&(P.basePhyStats()!=null)
			&&(!P.amDestroyed()))
			{
				if(toCataloged)
				{
					changeCatalogFlag(P,true);
					final CataData data=getCatalogData(P);
					if(data!=null)
						data.addReference(P);
				}
				else
				if(CMLib.flags().isCataloged(P))
				{
					changeCatalogFlag(P,false);
					final CataData data=getCatalogData(P);
					if(data!=null)
						data.delReference(P);
				}
			}
		}
	}

	protected void propogateShopChange(final ShopKeeper SK, final Set<Physical> ignored, final Physical cataP)
	{
		final boolean isMob=(cataP instanceof MOB);
		Environmental E=null;
		boolean changes=false;
		final Vector<Environmental> newShop=new Vector<Environmental>();
		for(final Iterator<Environmental> i=SK.getShop().getStoreInventory();i.hasNext();)
		{
			E=i.next();
			if(!ignored.contains(E) && (E instanceof Physical))
			{
				ignored.add((Physical)E);
				if((isMob)
				&&(E instanceof MOB)
				&&(CMLib.flags().isCataloged(E))
				&&(cataP.Name().equalsIgnoreCase(E.Name())))
				{
					E.setMiscText(E.text());
					changes = true;
				}
				if((!isMob)
				&&(E instanceof Item)
				&&(CMLib.flags().isCataloged(E))
				&&(cataP.Name().equalsIgnoreCase(E.Name())))
				{
					E.setMiscText(E.text());
					changes = true;
				}
			}
			newShop.add(E);
		}
		if(changes)
			SK.getShop().resubmitInventory(newShop);
	}

	@Override
	public CataData getCatalogData(final Physical P)
	{
		if(P==null)
			return null;
		return (P instanceof MOB)?getCatalogMobData(P.Name()):getCatalogItemData(P.Name());
	}

	@Override
	public Physical getCatalogObj(final Physical P)
	{
		if(P==null)
			return null;
		return (P instanceof MOB)?getCatalogMob(P.Name()):getCatalogItem(P.Name());
	}

	@Override
	public void setCategory(final Physical P, final String category)
	{
		final CataData data=getCatalogData(P);
		if((data!=null)&&(category!=null))
			data.setCategory(category);
	}

	@Override
	public void updateCatalogIntegrity(final Physical P)
	{
		synchronized(getSync(P).intern())
		{
			if(checkCatalogIntegrity(P)!=null)
			{
				changeCatalogFlag(P,false);
				P.text();
			}
		}
	}

	private final String getSync(final String name, final boolean mobType)
	{
		return ((mobType) ? "CATASYNC_MOB_" : "CATASYNC_ITEM_") + name.toUpperCase();
	}

	private final String getSync(final Environmental E)
	{
		return getSync(E.Name(), E instanceof MOB);
	}

	@Override
	public StringBuffer checkCatalogIntegrity(final Physical P)
	{
		if(P==null)
			return null;
		synchronized(getSync(P).intern())
		{
			if(CMLib.flags().isCataloged(P))
			{
				final CataData data=getCatalogData(P);
				final Physical cataE=getCatalogObj(P);
				if((cataE==null)||(data==null))
				{
					if(!CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))
						return null; // if catalog isn't fully loaded, this can be a false correction
					if(data!=null)
						data.delReference(P);
					changeCatalogFlag(P,false);
					P.text();
					return null;
				}
				else
				{
					StringBuffer diffs=null;
					changeCatalogFlag(P,false);
					if(!cataE.sameAs(P))
					{
						diffs=new StringBuffer("");
						for(int i=0;i<cataE.getStatCodes().length;i++)
						{
							if((!cataE.getStat(cataE.getStatCodes()[i]).equals(P.getStat(cataE.getStatCodes()[i]))))
								diffs.append(cataE.getStatCodes()[i]+",");
						}
					}
					if((P instanceof MOB)&&(cataE instanceof MOB))
					{
						boolean firstChecked=false;
						if(diffs==null)
							diffs=new StringBuffer("");
						for(final Pair<Clan,Integer> p : ((MOB)P).clans())
						{
							if((((MOB)cataE).getClanRole(p.first.clanID())==null)
							||(((MOB)cataE).getClanRole(p.first.clanID()).second.intValue()!=p.second.intValue()))
								firstChecked=true;
						}
						for(final Pair<Clan,Integer> p : ((MOB)cataE).clans())
						{
							if((((MOB)P).getClanRole(p.first.clanID())==null)
							||(((MOB)P).getClanRole(p.first.clanID()).second.intValue()!=p.second.intValue()))
								firstChecked=true;
						}
						if(firstChecked)
							diffs.append("CLANID,");
					}
					changeCatalogFlag(P,true);
					data.addReference(P);
					return diffs;
				}
			}
			else
			{
				final CataData data=getCatalogData(P);
				if(data!=null)
					data.delReference(P);
				return null;
			}
		}
	}

	@Override
	public Item getDropItem(final MOB M, final boolean live)
	{
		if(M==null)
			return null;
		CatalogLibrary.CataData data=null;
		List<Item> selections=null;
		synchronized(icatalog)
		{
			try
			{
				for(int d=0;d<icatalog.size();d++)
				{
					data=(CatalogLibrary.CataData)icatalog.elementAt(d,2);
					if((data.getRate()>0.0)
					&&(data.getWhenLive()==live)
					&&(Math.random() <= data.getRate())
					&&(CMLib.masking().maskCheck(data.getMaskV(),M,true)))
					{
						if(selections==null)
							selections=new ArrayList<Item>();
						selections.add((Item)icatalog.elementAt(d,1));
					}
				}
			}
			catch(final IndexOutOfBoundsException e)
			{
			}
		}
		if(selections==null)
			return null;
		Item I=selections.get(CMLib.dice().roll(1,selections.size(),-1));
		I=(Item)I.copyOf();
		changeCatalogUsage(I,true);
		return I;
	}

	@Override
	public CataData sampleCataData(final String xml)
	{
		return new CataDataImpl(xml);
	}

	@Override
	public boolean activate()
	{
		if(serviceClient==null)
		{
			name="THCatalog"+Thread.currentThread().getThreadGroup().getName().charAt(0);
			serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, MudHost.TIME_SAVETHREAD_SLEEP, 1);
		}
		return true;
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		try
		{
			if(!CMSecurity.isDisabled(CMSecurity.DisFlag.CATALOGTHREAD))
			{
				tickStatus=Tickable.STATUS_ALIVE;
				isDebugging=CMSecurity.isDebugging(DbgFlag.CATALOGTHREAD);
				setThreadStatus(serviceClient,"checking catalog references.");
				String[] names = getCatalogItemNames();
				for (final String name2 : names)
				{
					final CataData data=getCatalogItemData(name2);
					data.cleanHouse();
				}
				names = getCatalogMobNames();
				for (final String name2 : names)
				{
					final CataData data=getCatalogMobData(name2);
					data.cleanHouse();
				}
			}
		}
		finally
		{
			tickStatus=Tickable.STATUS_NOT;
			setThreadStatus(serviceClient,"sleeping");
		}
		return true;
	}

	@Override
	public boolean shutdown()
	{
		icatalog=new DVector(2);
		mcatalog=new DVector(2);
		if(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK))
		{
			CMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK);
			serviceClient=null;
		}
		return true;
	}

	protected void forceTick()
	{
		serviceClient.tickTicker(true);
	}

	protected static class RoomContentImpl implements RoomContent
	{
		private Physical 		obj=null;
		private boolean 		dirty=false;
		private Environmental	holder=null;

		public RoomContentImpl(final Physical P)
		{
			obj = P;
		}

		public RoomContentImpl(final Physical P, final Environmental E)
		{
			obj = P;
			holder = E;
		}

		@Override
		public Physical P()
		{
			return obj;
		}

		@Override
		public void flagDirty()
		{
			dirty = true;
		}

		@Override
		public Environmental holder()
		{
			return holder;
		}

		@Override
		public boolean isDirty()
		{
			return dirty;
		}

		@Override
		public boolean deleted()
		{
			return obj.amDestroyed();
		}
	}

	protected static class CataDataImpl implements CataData
	{
		public String 		lmaskStr			= null;
		public String		category			= "";
		public boolean 		live				= false;
		public double 		rate				= 0.0;
		public volatile int deathPickup			= 0;
		public SVector<WeakReference<Physical>>
							refs				= new SVector<WeakReference<Physical>>(1);
		public boolean 		noRefs = CMProps.getBoolVar(CMProps.Bool.CATALOGNOCACHE)
								  || CMSecurity.isDisabled(CMSecurity.DisFlag.CATALOGCACHE);
		public MaskingLibrary.CompiledZMask
							lmaskV				= null;

		public CataDataImpl(final String catadata)
		{
			build(catadata);
		}

		private Vector<Physical> makeVector()
		{
			final Vector<Physical> V=new Vector<Physical>(refs.size());
			WeakReference<Physical> R=null;
			for(int r=0;r<refs.size();r++)
			{
				R=refs.elementAt(r);
				if(R!=null)
				{
					final Physical o=R.get();
					if((o!=null)
					&&(!o.amDestroyed())
					&&(CMath.bset(o.basePhyStats().disposition(),PhyStats.IS_CATALOGED)))
						V.addElement(o);
				}
			}
			return V;
		}

		@Override
		public String category()
		{
			return category;
		}

		@Override
		public void setCategory(final String cat)
		{
			if(cat!=null)
				category=cat;
			else
				category="";
		}

		protected RoomnumberSet getLocations()
		{
			final Vector<Physical> items=makeVector();
			final RoomnumberSet homes=(RoomnumberSet)CMClass.getCommon("DefaultRoomnumberSet");
			final RoomnumberSet set=(RoomnumberSet)CMClass.getCommon("DefaultRoomnumberSet");
			Room R=null;
			for(final Environmental E : items)
			{
				R=CMLib.map().getStartRoom(E);
				if(R==null)
					R=CMLib.map().roomLocation(E);
				if(R==null)
					continue;
				if((E instanceof Item)
				&&(((Item)E).owner() instanceof MOB)
				&&(!((MOB)((Item)E).owner()).isMonster()))
					continue;
				if(CMLib.law().getLandTitle(R)!=null)
					homes.add(CMLib.map().getExtendedRoomID(R));
				else
					set.add(CMLib.map().getExtendedRoomID(R));
			}
			if(set.roomCountAllAreas()>0)
				return set;
			return homes;
		}

		@Override
		public String randomRoom()
		{
			final RoomnumberSet set=getLocations();
			if(set.roomCountAllAreas()==0)
				return "";
			return set.random();
		}

		@Override
		public String mostPopularArea()
		{
			final RoomnumberSet set=getLocations();
			final Iterator<String> e=set.getAreaNames();
			if(!e.hasNext())
				return "";
			String maxArea=e.next();
			int maxCount=set.roomCount(maxArea);
			for(;e.hasNext();)
			{
				final String area=e.next();
				final int count=set.roomCount(area);
				if(count>maxCount)
				{
					maxCount=count;
					maxArea=area;
				}
			}
			final Area A=CMLib.map().getArea(maxArea);
			if(A!=null)
				return A.Name();
			return maxArea;
		}

		@Override
		public int numReferences()
		{
			int num=0;
			for(int r=0;r<refs.size();r++)
			{
				if(refs.elementAt(r).get()!=null)
					num++;
			}
			return num;
		}

		@Override
		public Enumeration<Physical> enumeration()
		{
			return makeVector().elements();
		}

		@Override
		public int getDeathsPicksups()
		{
			return deathPickup;
		}

		@Override
		public void bumpDeathPickup()
		{
			deathPickup++;
		}

		@Override
		public synchronized void cleanHouse()
		{
			if(noRefs)
			{
				refs.clear();
				return;
			}
			Environmental o=null;
			try
			{
				for(int r=refs.size()-1;r>=0;r--)
				{
					o=refs.elementAt(r).get();
					if((o==null)||o.amDestroyed()||(!CMLib.flags().isCataloged(o)))
						refs.removeElementAt(r);
				}
			}
			catch (final ArrayIndexOutOfBoundsException ex)
			{
			}
		}

		@Override
		public Physical getLiveReference()
		{
			if(noRefs)
				return null;
			Physical o=null;
			try
			{
				for(int r=0;r<refs.size();r++)
				{
					o=refs.elementAt(r).get();
					if((o!=null)&&(CMLib.flags().isInTheGame(o,true)))
						return o;
				}
			}
			catch (final Exception e)
			{
			}
			return null;
		}

		@Override
		public synchronized void addReference(final Physical P)
		{
			if(noRefs)
				return;
			if(isReference(P))
				return;
			Environmental o=null;
			for(int r=0;r<refs.size();r++)
			{
				o=refs.elementAt(r).get();
				if(o==null)
				{
					refs.setElementAt(new WeakReference<Physical>(P),r);
					return;
				}
			}
			refs.addElement(new WeakReference<Physical>(P));
		}

		@Override
		public boolean isReference(final Physical P)
		{
			for(int r=0;r<refs.size();r++)
			{
				if(refs.elementAt(r).get()==P)
					return true;
			}
			return false;
		}

		@Override
		public synchronized void delReference(final Physical P)
		{
			if(!isReference(P))
				return;
			Environmental o=null;
			for(int r=0;r<refs.size();r++)
			{
				o=refs.elementAt(r).get();
				if(o==P)
				{
					refs.removeElementAt(r);
					return;
				}
			}
		}

		protected CataDataImpl(final String _lmask, final String _rate, final boolean _live)
		{
			this(_lmask,CMath.s_pct(_rate),_live);
		}

		protected CataDataImpl(final String _lmask, final double _rate, final boolean _live)
		{
			live=_live;
			lmaskStr=_lmask;
			lmaskV=null;
			if(lmaskStr.length()>0)
				lmaskV=CMLib.masking().maskCompile(lmaskStr);
			rate=_rate;
		}

		@Override
		public MaskingLibrary.CompiledZMask getMaskV()
		{
			return lmaskV;
		}

		@Override
		public String getMaskStr()
		{
			return lmaskStr;
		}

		@Override
		public boolean getWhenLive()
		{
			return live;
		}

		@Override
		public double getRate()
		{
			return rate;
		}

		@Override
		public void setMaskStr(final String s)
		{
			lmaskStr=s;
			if(s.trim().length()==0)
				lmaskV=null;
			else
				lmaskV=CMLib.masking().maskCompile(s);
		}

		@Override
		public void setWhenLive(final boolean l)
		{
			live = l;
		}

		@Override
		public void setRate(final double r)
		{
			rate = r;
		}

		@Override
		public String data(final String name)
		{
			final StringBuffer buf=new StringBuffer("");
			buf.append("<CATALOGDATA ");
			if(name != null)
				buf.append("NAME=\""+CMLib.xml().parseOutAngleBracketsAndQuotes(name)+"\" ");
			buf.append("CATAGORY=\""+CMLib.xml().parseOutAngleBracketsAndQuotes(category)+"\">");
			buf.append("<RATE>"+CMath.toPct(rate)+"</RATE>");
			buf.append("<LMASK>"+CMLib.xml().parseOutAngleBrackets(lmaskStr)+"</LMASK>");
			buf.append("<LIVE>"+live+"</LIVE>");
			buf.append("</CATALOGDATA>");
			return buf.toString();
		}

		@Override
		public void build(final String catadata)
		{
			List<XMLLibrary.XMLTag> V=null;
			if((catadata!=null)&&(catadata.length()>0))
			{
				V=CMLib.xml().parseAllXML(catadata);
				final XMLTag piece=CMLib.xml().getPieceFromPieces(V,"CATALOGDATA");
				if((piece!=null)&&(piece.contents()!=null)&&(piece.contents().size()>0))
				{
					category=CMLib.xml().restoreAngleBrackets(piece.getParmValue( "CATAGORY"));
					if(category==null)
						category="";
					lmaskStr=CMLib.xml().restoreAngleBrackets(piece.getValFromPieces("LMASK"));
					final String ratestr=piece.getValFromPieces("RATE");
					rate=CMath.s_pct(ratestr);
					lmaskV=null;
					if(lmaskStr.length()>0)
						lmaskV=CMLib.masking().maskCompile(lmaskStr);
					live=CMath.s_bool(piece.getValFromPieces("LIVE"));
				}
			}
			else
			{
				lmaskV=null;
				lmaskStr="";
				live=false;
				rate=0.0;
			}
		}
	}

	protected CMFile.CMVFSDir getCatalogMobsRoot(final CMFile.CMVFSDir rootRoot)
	{
		if(catalogFileMobsRoot == null)
		{
			final CMFile.CMVFSDir newRoot=getCatalogRoot(mcatalog, "mobs", rootRoot);
			if(newRoot==null)
				return null;
			catalogFileMobsRoot=newRoot;
		}
		return catalogFileMobsRoot;
	}

	protected CMFile.CMVFSDir getCatalogItemsRoot(final CMFile.CMVFSDir rootRoot)
	{
		if(catalogFileItemsRoot == null)
		{
			final CMFile.CMVFSDir newRoot=getCatalogRoot(icatalog, "items", rootRoot);
			if(newRoot==null)
				return null;
			catalogFileItemsRoot=newRoot;
		}
		return catalogFileItemsRoot;
	}

	@Override
	public CMFile.CMVFSDir getCatalogRoot(final CMFile.CMVFSDir root)
	{
		return new CMFile.CMVFSDir(root,root.getPath()+"catalog/")
		{
			private CMFile.CMVFSFile[]	myFiles		= null;
			private CMFile.CMVFSFile[]	oldFiles	= null;

			@Override
			protected CMFile.CMVFSFile[] getFiles()
			{
				if((myFiles==null)||(oldFiles!=super.files)||(catalogFileItemsRoot==null)||(catalogFileMobsRoot==null))
				{
					oldFiles=super.files;
					final CMFile.CMVFSDir mdir = getCatalogMobsRoot(this);
					final CMFile.CMVFSDir idir = getCatalogItemsRoot(this);
					final int xtra=((mdir==null)?0:1)+((idir==null)?0:1);
					if(super.files!=null)
						myFiles=Arrays.copyOf(super.files, super.files.length+xtra);
					else
						myFiles=new CMFile.CMVFSFile[xtra];
					if(xtra==2)
					{
						myFiles[myFiles.length-2]=mdir;
						myFiles[myFiles.length-1]=idir;
					}
					else
					if(mdir != null)
						myFiles[myFiles.length-1]=mdir;
					else
					if(idir != null)
						myFiles[myFiles.length-1]=idir;
					Arrays.sort(myFiles,CMFile.CMVFSDir.fcomparator);
				}
				return myFiles;
			}
		};
	}

	protected CMFile.CMVFSDir getCatalogRoot(final DVector catalog, final String rootName, final CMFile.CMVFSDir rootRoot)
	{
		if(catalog.size()==0)
			return null;
		final CMFile.CMVFSDir catalogFileRoot=new CMFile.CMVFSDir(rootRoot, 48, rootRoot.getPath()+rootName+"/");

		final HashMap<String,List<Physical>> usedCats=new HashMap<String,List<Physical>>();
		for(int i=0;i<catalog.size();i++)
		{
			final Physical obj=(Physical)catalog.elementAt(i, 1);
			final CataData data=(CataData)catalog.elementAt(i, 2);

			catalogFileRoot.add(new CMFile.CMVFSFile(catalogFileRoot.getPath()+obj.Name().replace(' ','_')+".cmare",48,System.currentTimeMillis(),"SYS")
			{
				@Override
				public Object readData()
				{
					if(obj instanceof MOB)
						return CMLib.coffeeMaker().getMobXML((MOB)obj);
					else
					if(obj instanceof Item)
						return CMLib.coffeeMaker().getItemXML((Item)obj);
					else
						return null;
				}
			});

			if(!usedCats.containsKey(data.category()))
				usedCats.put(data.category(), new Vector<Physical>());
			final List<Physical> list=usedCats.get(data.category());
			list.add(obj);
		}
		catalogFileRoot.add(new CMFile.CMVFSFile(catalogFileRoot.getPath()+"all.cmare",48,System.currentTimeMillis(),"SYS")
		{
			@Override
			public Object readData()
			{
				final String tagName=(catalog.elementAt(0,1) instanceof MOB)?"MOBS":"ITEMS";
				final StringBuilder str=new StringBuilder("<"+tagName+">");
				for(int i=0;i<catalog.size();i++)
				{
					final Physical obj=(Physical)catalog.elementAt(i, 1);
					if(obj instanceof MOB)
						str.append(CMLib.coffeeMaker().getMobXML((MOB)obj));
					else
					if(obj instanceof Item)
						str.append(CMLib.coffeeMaker().getItemXML((Item)obj));
				}
				str.append("</"+tagName+">");
				return str.toString();
			}
		});
		for(final String cat : usedCats.keySet())
		{
			CMFile.CMVFSDir catagoryRoot=catalogFileRoot;
			if(cat.length()==0)
			{
				catagoryRoot=new CMFile.CMVFSDir(catalogFileRoot, 48, catalogFileRoot.getPath()+"uncategorized/");
				catalogFileRoot.add(catagoryRoot);
			}
			else
			{
				catagoryRoot=new CMFile.CMVFSDir(catalogFileRoot, 48, catalogFileRoot.getPath()+cat.toLowerCase()+"/");
				catalogFileRoot.add(catagoryRoot);
			}
			final List<Physical> objs=usedCats.get(cat);
			if(objs.size()>0)
			{
				for(final Physical obj : objs)
				{
					catagoryRoot.add(new CMFile.CMVFSFile(catagoryRoot.getPath()+obj.Name().replace(' ','_')+".cmare",48,System.currentTimeMillis(),"SYS")
					{
						@Override
						public Object readData()
						{
							if(obj instanceof MOB)
								return CMLib.coffeeMaker().getMobXML((MOB)obj);
							else
							if(obj instanceof Item)
								return CMLib.coffeeMaker().getItemXML((Item)obj);
							else
								return null;
						}
					});
				}
			}
			catagoryRoot.add(new CMFile.CMVFSFile(catagoryRoot.getPath()+"all.cmare",48,System.currentTimeMillis(),"SYS")
			{
				@Override
				public Object readData()
				{
					final String tagName=(objs.get(0) instanceof MOB)?"MOBS":"ITEMS";
					final StringBuilder str=new StringBuilder("<"+tagName+">");
					for(final Physical obj : objs)
					{
						if(obj instanceof MOB)
							str.append(CMLib.coffeeMaker().getMobXML((MOB)obj));
						else
						if(obj instanceof Item)
							str.append(CMLib.coffeeMaker().getItemXML((Item)obj));
					}
					str.append("</"+tagName+">");
					return str.toString();
				}
			});
		}
		return catalogFileRoot;
	}
	
	@Override
	public String makeValidNewBuilderTemplateID(final String ID)
	{
		if((ID==null)||(ID.trim().length()==0)||(ID.indexOf(' ')>=0))
			return null;
		int x=ID.indexOf('_');
		if(x<0)
			return ID.toUpperCase().trim();
		if(x==0)
			return null;
		String possPlayer = ID.substring(0,x);
		if(CMLib.players().playerExists(possPlayer)||CMLib.players().accountExists(possPlayer))
			return null;
		return ID.toUpperCase().trim();
	}
	
	public Map<String,PlayerData> getBuilderTemplates(final String playerName)
	{
		final Map<String, PlayerData> allMyTemplates=new Hashtable<String, PlayerData>();
		if((playerName==null)
		||(playerName.length()==0))
			return allMyTemplates;
		List<PlayerData> pDat = CMLib.database().DBReadPlayerData(playerName, templatePersonalSection);
		List<PlayerData> sDat = CMLib.database().DBReadPlayerSectionData(templateSharedSection);
		for(final PlayerData PD : pDat)
			allMyTemplates.put(PD.key().substring(commonBuilderTemplateKey.length()+1+PD.who().length()+1).toUpperCase().trim(), PD);
		for(final PlayerData PD : sDat)
		{
			if(PD.who().equalsIgnoreCase(playerName))
				allMyTemplates.put(PD.key().substring(commonBuilderTemplateKey.length()+1+PD.who().length()+1).toUpperCase().trim().trim(), PD);
			else
				allMyTemplates.put(PD.key().substring(commonBuilderTemplateKey.length()+1).toUpperCase().trim(), PD);
		}
		return allMyTemplates;
	}

	@Override
	public List<Triad<String, String, String>> getBuilderTemplateList(final String playerName)
	{
		List<Triad<String, String, String>> list = new Vector<Triad<String, String, String>>();
		final Map<String, PlayerData> PDs=getBuilderTemplates(playerName);
		if((PDs!=null)&&(PDs.size()>0))
		{
			for(final String ID : PDs.keySet())
			{
				final PlayerData pData = PDs.get(ID);
				CMClass.CMObjectType typ=CMLib.coffeeMaker().getUnknownTypeFromXML(pData.xml());
				if(typ == null)
					typ=CMClass.CMObjectType.WEBMACRO;
				final String typName;
				if(!pData.who().equalsIgnoreCase(playerName))
					typName="*"+typ.toString();
				else
				if(pData.section().equals(templateSharedSection))
					typName="+"+typ.toString();
				else
					typName=" "+typ.toString();
				final String name=CMLib.coffeeMaker().getUnknownNameFromXML(pData.xml());
				list.add(new Triad<String, String, String>(ID,typName,name));
			}
		}
		return list;
	}
	
	@Override
	public Environmental getBuilderTemplateObject(final String playerName, final String ID)
	{
		if((ID==null)||(ID.length()==0))
			return null;
		final PlayerData PD=getBuilderTemplates(playerName).get(ID.toUpperCase().trim());
		if(PD==null)
			return null;
		return CMLib.coffeeMaker().getUnknownFromXML(PD.xml());
	}
	
	@Override
	public boolean addNewBuilderTemplateObject(final String playerName, final String ID, final Environmental E)
	{
		final StringBuffer xml=CMLib.coffeeMaker().getUnknownXML(E);
		if(xml==null)
			return false;
		CMLib.database().DBCreatePlayerData(playerName, templatePersonalSection, commonBuilderTemplateKey+"_"+playerName.toUpperCase().trim()+"_"+ID.toUpperCase().trim(), xml.toString());
		return true;
	}
	
	@Override
	public boolean deleteBuilderTemplateObject(final String playerName, final String ID)
	{
		CMLib.database().DBDeletePlayerData(playerName, templatePersonalSection, commonBuilderTemplateKey+"_"+playerName.toUpperCase().trim()+"_"+ID.toUpperCase().trim());
		CMLib.database().DBDeletePlayerData(playerName, templateSharedSection, commonBuilderTemplateKey+"_"+playerName.toUpperCase().trim()+"_"+ID.toUpperCase().trim());
		return true;
	}
	
	@Override
	public boolean toggleBuilderTemplateObject(final String playerName, final String ID)
	{
		if((ID==null)||(ID.length()==0))
			return false;
		final PlayerData PD=getBuilderTemplates(playerName).get(ID.toUpperCase().trim());
		if(PD==null)
			return false;
		if(!PD.who().equalsIgnoreCase(playerName))
			return false;
		if(PD.section().equals(templatePersonalSection))
		{
			CMLib.database().DBDeletePlayerData(PD.who(), PD.section(), PD.key());
			CMLib.database().DBCreatePlayerData(PD.who(), templateSharedSection, PD.key(), PD.xml());
			return true;
		}
		else
		if(PD.section().equals(templateSharedSection))
		{
			CMLib.database().DBDeletePlayerData(PD.who(), PD.section(), PD.key());
			CMLib.database().DBCreatePlayerData(PD.who(), templatePersonalSection, PD.key(), PD.xml());
			return true;
		}
		else
			return false;
	}
}