class DeviceManagerImpl implements IDeviceService, IOFMessageListener, ITopologyListener, IFloodlightModule, IEntityClassListener, IInfoProvider {

	protected Device learnDeviceByEntity(Entity entity) {
		ArrayList<Long> deleteQueue = null;
		LinkedList<DeviceUpdate> deviceUpdates = null;
		Device device = null;

		// we may need to restart the learning process if we detect
		// concurrent modification.  Note that we ensure that at least
		// one thread should always succeed so we don't get into infinite
		// starvation loops
		while (true) {
			deviceUpdates = null;

			// Look up the fully-qualified entity to see if it already
			// exists in the primary entity index.
			Long deviceKey = primaryIndex.findByEntity(entity);
			IEntityClass entityClass = null;

			if (deviceKey == null) {
				// If the entity does not exist in the primary entity index,
				// use the entity classifier for find the classes for the
				// entity. Look up the entity in the returned class'
				// class entity index.
				entityClass = entityClassifier.classifyEntity(entity);
				if (entityClass == null) {
					// could not classify entity. No device
					device = null;
					break;
				}
				ClassState classState = getClassState(entityClass);

				if (classState.classIndex != null) {
					deviceKey = classState.classIndex.findByEntity(entity);
				}
			}
			if (deviceKey != null) {
				// If the primary or secondary index contains the entity
				// use resulting device key to look up the device in the
				// device map, and use the referenced Device below.
				device = deviceMap.get(deviceKey);
				if (device == null) {
					// This can happen due to concurrent modification
					if (logger.isDebugEnabled()) {
						logger.debug("No device for deviceKey {} while "
								+ "while processing entity {}",
								deviceKey, entity);
					}
					// if so, then try again till we don't even get the device key
					// and so we recreate the device
					continue;
				}
			} else {
				// If the secondary index does not contain the entity,
				// create a new Device object containing the entity, and
				// generate a new device ID if the the entity is on an
				// attachment point port. Otherwise ignore.
				if (entity.hasSwitchPort() && !topology.isAttachmentPointPort(entity.getSwitchDPID(), entity.getSwitchPort())) {
					cntDeviceOnInternalPortNotLearned.increment();
					if (logger.isDebugEnabled()) {
						logger.debug("Not learning new device on internal"
								+ " link: {}", entity);
					}
					device = null;
					break;
				}
				// Before we create the new device also check if
				// the entity is allowed (e.g., for spoofing protection)
				if (!isEntityAllowed(entity, entityClass)) {
					cntPacketNotAllowed.increment();
					if (logger.isDebugEnabled()) {
						logger.debug("PacketIn is not allowed {} {}",
								entityClass.getName(), entity);
					}
					device = null;
					break;
				}
				deviceKey = deviceKeyCounter.getAndIncrement();
				device = allocateDevice(deviceKey, entity, entityClass);


				// Add the new device to the primary map with a simple put
				deviceMap.put(deviceKey, device);
				// update indices
				if (!updateIndices(device, deviceKey)) {
					if (deleteQueue == null)
						deleteQueue = new ArrayList<Long>();
					deleteQueue.add(deviceKey);
					continue;
				}

				updateSecondaryIndices(entity, entityClass, deviceKey);

				// We need to count and log here. If we log earlier we could
				// hit a concurrent modification and restart the dev creation
				// and potentially count the device twice.
				cntNewDevice.increment();
				if (logger.isDebugEnabled()) {
					logger.debug("New device created: {} deviceKey={}, entity={}",
							new Object[]{device, deviceKey, entity});
				}
				// generate new device update
				deviceUpdates = updateUpdates(deviceUpdates, new DeviceUpdate(device, ADD, null));

				break;
			}
			// if it gets here, we have a pre-existing Device for this Entity
			if (!isEntityAllowed(entity, device.getEntityClass())) {
				cntPacketNotAllowed.increment();
				if (logger.isDebugEnabled()) {
					logger.info("PacketIn is not allowed {} {}",
							device.getEntityClass().getName(), entity);
				}
				return null;
			}
			// If this is not an attachment point port we don't learn the new entity
			// and don't update indexes. But we do allow the device to continue up
			// the chain.
			if (entity.hasSwitchPort() && !topology.isAttachmentPointPort(entity.getSwitchDPID(), entity.getSwitchPort())) {
				cntPacketOnInternalPortForKnownDevice.increment();
				break;
			}
			int entityindex = -1;
			if ((entityindex = device.entityIndex(entity)) >= 0) {
				// Entity already exists
				// update timestamp on the found entity
				Date lastSeen = entity.getLastSeenTimestamp();
				if (lastSeen == null) {
					lastSeen = new Date();
					entity.setLastSeenTimestamp(lastSeen);
				}
				device.entities[entityindex].setLastSeenTimestamp(lastSeen);
				// we break the loop after checking for changes to the AP
			} else {
				// New entity for this device
				// compute the insertion point for the entity.
				// see Arrays.binarySearch()
				entityindex = -(entityindex + 1);
				Device newDevice = allocateDevice(device, entity, entityindex);

				// generate updates
				EnumSet<DeviceField> changedFields = findChangedFields(device, entity);

				// update the device map with a replace call
				boolean res = deviceMap.replace(deviceKey, device, newDevice);
				// If replace returns false, restart the process from the
				// beginning (this implies another thread concurrently
				// modified this Device).
				if (!res)
					continue;

				device = newDevice;
				// update indices
				if (!updateIndices(device, deviceKey)) {
					continue;
				}
				updateSecondaryIndices(entity,
						device.getEntityClass(),
						deviceKey);

				// We need to count here after all the possible "continue"
				// statements in this branch
				cntNewEntity.increment();
				if (changedFields.size() > 0) {
					cntDeviceChanged.increment();
					deviceUpdates =
							updateUpdates(deviceUpdates,
									new DeviceUpdate(newDevice, CHANGE,
											changedFields));
				}
				// we break the loop after checking for changed AP
			}
			// Update attachment point (will only be hit if the device
			// already existed and no concurrent modification)
			if (entity.hasSwitchPort()) {
				boolean moved = device.updateAttachmentPoint(entity.getSwitchDPID(),
						entity.getSwitchPort(),
						entity.getLastSeenTimestamp());
				// TODO: use update mechanism instead of sending the
				// notification directly
				if (moved) {
					// we count device moved events in sendDeviceMovedNotification()
					sendDeviceMovedNotification(device);
					if (logger.isTraceEnabled()) {
						logger.trace("Device moved: attachment points {}," +
								"entities {}", device.attachmentPoints,
								device.entities);
					}
				} else {
					if (logger.isTraceEnabled()) {
						logger.trace("Device attachment point updated: " +
								"attachment points {}," +
								"entities {}", device.attachmentPoints,
								device.entities);
					}
				}
			}
			break;
		}

		if (deleteQueue != null) {
			for (Long l : deleteQueue) {
				Device dev = deviceMap.get(l);
				this.deleteDevice(dev);
			}
		}
		processUpdates(deviceUpdates);
		deviceSyncManager.storeDeviceThrottled(device);

		return device;
	}

}