/*
* Copyright (C) Tony Green, Litepal Framework Open Source Project
*
* 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 org.litepal.crud;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import org.litepal.LitePalBase;
import org.litepal.crud.model.AssociationsInfo;
import org.litepal.tablemanager.Connector;
import org.litepal.util.BaseUtility;
import org.litepal.util.DBUtility;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
/**
* Deals analysis work when comes to two models are associated with Many2Many
* associations.
*
* @author Tony Green
* @since 1.1
*/
public class Many2ManyAnalyzer extends AssociationsAnalyzer {
/**
* Analyzing the AssociationInfo. It will help baseObj assign the necessary
* values automatically. If the two associated models have bidirectional
* associations in class files but developer has only build unidirectional
* associations in models, it will force to build the bidirectional
* associations. Besides the
* {@link DataSupport#addAssociatedModelForJoinTable(String, long)} will be called
* here to put right values into tables.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associationInfo
* The associated info analyzed by
* {@link LitePalBase#getRelatedInfo}.
* @throws SecurityException
* @throws IllegalArgumentException
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException,
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
Collection<DataSupport> associatedModels = getAssociatedModels(baseObj, associationInfo);
declareAssociations(baseObj, associationInfo);
if (associatedModels != null) {
for (DataSupport associatedModel : associatedModels) {
Collection<DataSupport> tempCollection = getReverseAssociatedModels(
associatedModel, associationInfo);
Collection<DataSupport> reverseAssociatedModels = checkAssociatedModelCollection(
tempCollection, associationInfo.getAssociateSelfFromOtherModel());
addNewModelForAssociatedModel(reverseAssociatedModels, baseObj);
setReverseAssociatedModels(associatedModel, associationInfo,
reverseAssociatedModels);
dealAssociatedModel(baseObj, associatedModel);
}
}
}
/**
* This add an empty set for {@link DataSupport#associatedIdsM2M}. Might use
* for updating intermediate join table.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associationInfo
* To get associated table name from.
*/
private void declareAssociations(DataSupport baseObj, AssociationsInfo associationInfo) {
baseObj.addEmptyModelForJoinTable(getAssociatedTableName(associationInfo));
}
/**
* Force to build bidirectional associations for the associated model. If it
* has already built, ignoring the rest process.
*
* @param associatedModelCollection
* The associated models collection of the associated model. Add
* self model into it if it doesn't contain self model yet.
* @param baseObj
* The baseObj currently want to persist or update.
*/
private void addNewModelForAssociatedModel(Collection<DataSupport> associatedModelCollection,
DataSupport baseObj) {
if (!associatedModelCollection.contains(baseObj)) {
associatedModelCollection.add(baseObj);
}
}
/**
* First of all the associated model need to be saved already, or nothing
* will be executed below. Then add the id of associated model into
* {@link DataSupport#associatedIdsM2M} for inserting value into
* intermediate join table after baseObj is saved.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associatedModel
* The associated model of baseObj.
*/
private void dealAssociatedModel(DataSupport baseObj, DataSupport associatedModel) {
if (associatedModel.isSaved()) {
baseObj.addAssociatedModelForJoinTable(associatedModel.getTableName(),
associatedModel.getBaseObjId());
}
}
/**
* Get the associated table name by {@link AssociationsInfo} after case
* changed.
*
* @param associationInfo
* To get the associated table name from.
* @return The name of associated table with changed case.
*/
private String getAssociatedTableName(AssociationsInfo associationInfo) {
return BaseUtility.changeCase(DBUtility.getTableNameByClassName(associationInfo
.getAssociatedClassName()));
}
/**
* Check if the associations between self model and associated model is
* already saved into intermediate join table.<br>
* Make sure baseObj and associatedModel are saved already, or the result
* might be wrong.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associatedModel
* The associated model of baseObj.
* @return If the associations between them is saved into intermediate join
* table, return true. Otherwise return false.
*/
@SuppressWarnings("unused")
@Deprecated
private boolean isDataExists(DataSupport baseObj, DataSupport associatedModel) {
boolean exists = false;
SQLiteDatabase db = Connector.getDatabase();
Cursor cursor = null;
try {
cursor = db.query(getJoinTableName(baseObj, associatedModel), null,
getSelection(baseObj, associatedModel),
getSelectionArgs(baseObj, associatedModel), null, null, null);
exists = cursor.getCount() > 0;
} catch (Exception e) {
e.printStackTrace();
return true;
} finally {
cursor.close();
}
return exists;
}
/**
* Build the selection for querying the data in table. Column names are the
* table names with _id as suffix.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associatedModel
* The associated model of baseObj.
* @return The selection clause for querying data.
*/
private String getSelection(DataSupport baseObj, DataSupport associatedModel) {
StringBuilder where = new StringBuilder();
where.append(getForeignKeyColumnName(baseObj.getTableName()));
where.append(" = ? and ");
where.append(getForeignKeyColumnName(associatedModel.getTableName()));
where.append(" = ?");
return where.toString();
}
/**
* Build the selection arguments to fill selection clause.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associatedModel
* The associated model of baseObj.
* @return The selection arguments with the id of baseObj and
* associatedModel to fill.
*/
private String[] getSelectionArgs(DataSupport baseObj, DataSupport associatedModel) {
return new String[] { String.valueOf(baseObj.getBaseObjId()),
String.valueOf(associatedModel.getBaseObjId()) };
}
/**
* Get the intermediate join table name for self model and associated model.
*
* @param baseObj
* The baseObj currently want to persist or update.
* @param associatedModel
* The associated model of baseObj.
* @return The intermediate join table name.
*/
private String getJoinTableName(DataSupport baseObj, DataSupport associatedModel) {
return getIntermediateTableName(baseObj, associatedModel.getTableName());
}
}